Valhalla Legends Forums Archive | Assembly Language (any cpu) | Funny calling convention (using esi/edi)

AuthorMessageTime
iago
So while I was writing that tutorial posted here, I was playing around in Starcraft. I noticed a couple functions that had funny calling conventions. For example, I notice parameters being passed in eax, occasionally, and once (shown below), parameters are passed in esi and edi.

Is using esi and edi for parameters some known calling convention? Or Is that an optimization?

Here's the code I noticed:
[code].text:0041F060 ; vsnprintf_wrapper
.text:0041F060
.text:0041F060 arg_0          = dword ptr  8
.text:0041F060 arg_4          = byte ptr  0Ch
.text:0041F060
.text:0041F060                push    ebp
.text:0041F061                mov    ebp, esp
.text:0041F063                mov    ecx, [ebp+arg_0]
.text:0041F066                lea    eax, [ebp+arg_4]
.text:0041F069                push    eax            ; va_list
.text:0041F06A                push    ecx            ; char *
.text:0041F06B                push    esi            ; size_t
.text:0041F06C                push    edi            ; char *
.text:0041F06D                call    __vsnprintf
.text:0041F072                add    esp, 10h
.text:0041F075                mov    byte ptr [edi+esi-1], 0
.text:0041F07A                pop    ebp
.text:0041F07B                retn
.text:0041F07B vsnprintf_wrapper endp
[/code]
April 10, 2007, 2:52 AM
JoeTheOdd
I was under the assumption that as long as their pushed on to the stack it doesn't matter where they came from.
April 10, 2007, 4:26 AM
Hell-Lord
I forget what it is exactly but it has something to do with constant value(s).
April 10, 2007, 4:46 AM
iago
[quote author=Joe[x86] link=topic=16602.msg167771#msg167771 date=1176179194]
I was under the assumption that as long as their pushed on to the stack it doesn't matter where they came from.
[/quote]
I'm not talking about the function called there, I'm talking about where esi and edi came from. They aren't assigned anywhere in that function, they're assigned when calling it.

[quote author=Hell-Lord link=topic=16602.msg167774#msg167774 date=1176180396]
I forget what it is exactly but it has something to do with constant value(s).
[/quote]
Hmm, that's possible. But I've looked at this function before in older versions, and that never used to happen.
April 11, 2007, 1:56 PM
JoeTheOdd
.text:0041F075                mov    byte ptr [edi+esi-1], 0

Apparently it is constant values then, because that appears to be setting [edi+esi] to (what will look like) a null string.
April 11, 2007, 7:22 PM
Myndfyr
[quote author=Joe[x86] link=topic=16602.msg167835#msg167835 date=1176319343]
.text:0041F075                 mov     byte ptr [edi+esi-1], 0

Apparently it is constant values then, because that appears to be setting [edi+esi] to (what will look like) a null string.
[/quote]

No.  Look at the arguments to __vsnprintf and the documentation for the equivalent functions (pulled from MSDN documentation):
[code]
int vswprintf(
  wchar_t *buffer,
  size_t count,
  const wchar_t *format,
  va_list argptr
);
[/code]
edi is clearly storing the destination string buffer.
esi is the size of the destination buffer.
ecx is the constant input format string.
eax holds the varargs argument list.

The purpose of the line you quoted:
[code]
mov byte ptr [edi+esi-1], 0
[/code] should be obvious to you based on what those arguments are.
edi+esi-1 is the address of very last character in the output string.  This sets it to null, presumably to ensure that if there's a buffer overflow, the result doesn't overflow another buffer.

In any case, that doesn't really have anything to do with iago's question, which is - since when are edi and esi used in a calling convention?

@iago: I would guess that there was probably some kind of modified pseudo-inline fastcall calling convention, or perhaps it's a result of storing register values?
April 11, 2007, 9:14 PM
Hell-Lord
esi is the position is it not?
April 12, 2007, 12:58 AM
Skywing
This is due to advanced compiler optimizations on module-internal functions, where the compiler knows of all callers of a particular function and can then apply custom calling conventions to allow for better register use across callers.

Functions that are `externally visible' will not have such optimizations applied to them.

Matt Pietrek authored an article providing a basic overview of these sort of optimizations as implemented in cl 13.  Some of the information in that article is dated and not entirely accurate now, but it conveys the basic idea.

In versions of cl prior to 13, there are a couple of instances where you might see strange calling conventions in compiler generated code.  For instance, for a number of releases, the `_alloca' special function was implemented using a custom calling convention using `eax' as an argument register and altering the stack pointer of the caller.  The various versions of `_SEH_prolog' also use custom calling conventions.
April 12, 2007, 2:13 AM
iago
Aha, so it is a weird optimization. I'll definitely have a look at that paper.

Thanks!
April 12, 2007, 2:27 AM
JoeTheOdd
Just for the record, is this similar to fastcall using different registers?
May 18, 2007, 6:58 AM
warz
[quote author=Joe[x86] link=topic=16602.msg169191#msg169191 date=1179471490]
Just for the record, is this similar to fastcall using different registers?
[/quote]

no, not traditionally. fastcall uses ecx and edx to pass the first two arguments, and the rest are passed through the stack. sometimes you'll see fastcall use eax, too, but i think this is a borland optimization. in broodwar's case, youll only see fastcall using ecx, for the most part.

on x64 systems, though, most calling conventions, such as stdcall, fastcall, etc, are ignored by the compiler, and the convention it uses is similar to fastcall. it passes the first four arguments by way of registers. sounds like it'd make debugging much nicer.

edit: see this
May 18, 2007, 12:43 PM
iago
[quote author=Joe[x86] link=topic=16602.msg169191#msg169191 date=1179471490]
Just for the record, is this similar to fastcall using different registers?
[/quote]
I'm not really sure what warz is talking about, although I think he either didn't read your post, or he's agreeing with you in an odd way. So I'll answer.

Yes, it's absolutely identical to __fastcall except that it uses different registers.
May 18, 2007, 2:21 PM
warz
If you're talking about the __vsnprintf call, it isn't identical to fastcall at all.
May 18, 2007, 6:30 PM
iago
Except that the defining characteristic of fastcall is that parameters are passed in registers, and that function passes parameters in registers.

He specifically asked if it's the same as fastcall but with different registers. Are you saying that's not true?
May 18, 2007, 9:22 PM
Skywing
It is not a fixed calling convention.  The compiler takes a look at the function itself, and all callers of the function, and from there decides the best way to pass parameter values.  It is subject to changing completely at a recompile.
May 19, 2007, 3:41 PM
warz
yeah, that's true.
May 19, 2007, 3:58 PM

Search