Alright last part, calling C function from asm, finally got it worked after lot of trouble, here is how it got it working:
it was one hell of a journey to destination.
Step1. Calling C without parameter and printing out the return value
I focused today on asm with main() entry point in asm plus C code, which means once final binary is executed, it starts from main declared in asm.
So, I attempted calling C function from asm and print out and return values.
Following wiki has fairly good reference for this purpose:
https://en.wikipedia.org/wiki/X86_calling_conventionsThis worked provided following conditions are met:
- functions in C, once compiled with turbo C TCC, I can see it adds _ underscore to function name when looking inside object file. So function name was <fcnName>, then the corresponding obj file contains _<fcnName>. Therefore when calling from asm code, you call as
call _<fcnName>
Return values from the function will be saved in eax.
So with following C and asm snipped of code will print out the abcd when asm file calls M_PRINTWORD ax:
C file:
int extFcnC()
{
return 0xabcd;
}asm file:
...
M_PRINTF "\ncalling extFncC..."
call _extFcnC
M_PRINTWORD ax
...M_PRINTWORD is a macro which prints out the parameter. Since C function return 0xabcd and return is saved in eax, then printing ax results in abcd
Here is the resulting output:
C:\git\minix.dev\exp>asmc
...
calling extFncC...ABCD
C:\git\minix.dev\exp>
Step2. Calling C WITH parameter and printing out the return value
For the next step, I modified the C code so that, it will accept 3 parameters and will do various arithmetic operation and based on the returned result, figure out the calling convention:
C file:
int extFcnC(int a, int b, int c)
{
int ret;
ret = a+b;
return ret;
}Asm file:
M_PRINTF "\ncalling extFncC..."
push ebp
mov ebp, esp
push 10h
push 20h
push 80h
call _extFcnC
add esp, 12
M_PRINTDWORD eaxHere are serise of variation in arithmetic in C (bold blue above code changed) and returned results are logged
1)
int ret;
ret = a+b;
return ret;eax was 080h. in this case, it printed out the third parameter pushed. It is unclear how it got?
2)
return a;
eax was 00h, in this case, a is not what it was supposed to be.
3)
return b;
eax was 080h, which means, it was third parameter.
At this point, I came to conclusion easily that following two variant resulted in same:
1) return b
2) return (a+b)
But we know return a was 0, which means push 80h was translated into parameter b.
4)
return c;
eax was 020h, which means, it was second parameter.
So using the C calling convention in the wiki above, it almost worked but not quite.
What I hoped was, series of pushes below will be assigned to parameter a, b and c respectively.
push 10h
push 20h
push 80h
Instead, I got following assignments:
push 10h -> nothing
push 20h -> parameter C
push 80h -> parameter B
So whole thing was off by word. I suspected this might have something to do with "near and far"-ness of the call
Conclusion:
This anomaly was caused by NEAR and FAR ness of the call. I found using the GRDB because the old-styled dos debugger was not able to correctly de-assemble some of the code correctly.
So here is what happens:
just before calling the extFcnC C function, I inspected the stack an BP, in another words, after pushing 10, 20 and 80 onto stack:
value were:
SP=F8 pointing to mem address: 80 00 20 00 10 00
BP=FE was point right past 80 00 20 00 10 00.Now once I step through i discover following:
one step ahead and you are in C function.
At that point, the SP changed to F4 from F8. That means CS:IP must have been pushed meaning it is FARCALL.
after that it pushes BP (SS=F2) and mov bp, sp (SS=BP=F2) and
in preparation of returning the variable in case of A: it was doing
mov ax, bp[4]
that would be bp[4] = F6. But what is at F6? Lets see what the BP and SP both pointing at:
BP/SP = F2 -> BP CS IP 0080 0020 0010You can see bp[4] is just a IP content. In other words, it fell 2 byte short of reaching the 0080 correct value.
By the same token
return b and c will return bp[6] and bp[8] respectively and in that instance will return 0080 0020 which is consistent with what I observed.
So here is the solution:
First of all, I have tried lot of different varieties to make it work but compiler refuse to compile it right. It is all off by a word or so and can not zero in into the right parameters:
- make extFncC as -> int _far extFncC() and declared it as EXTERNDEF:extFncC:far
Once done this way, both asm and C compiler will treat as a far call and far function and will push CS:IP onto stack but during the disassembly it was obvious compiler did it in such a way it pushes extra word 00 00 onto stack along with each of IP and CS so it looks as follows:
19 00 00 00 79 15 00 00 80 00 20 00 10 00
19 00 - IP[08] AND 00 00 SOME EXTRA 0-S I DONT GET IT
79 15 - CS AND 00 00 ANOTHER WORD OF EXTRA 0-S I DONT GET.
After lot of haggling around, made the "dirty" far call macro which is my last fool proof top gun

:
(it was not my idea but i just remembered the way one of our top sr. engineers did a decade ago, and i am just blessed to have it recalled)
In this instance, declare C as far:
int _far extFcnC(int a, int b, int c)
Declare it in asm as far too:
externdef _extFcnC:FAR
But when calling use a macro:
M_FARCALL MACRO pFncAddr
local retAddr
push cs
push retAddr
jmp pFncAddr
retAddr:
ENDMand now make a dirty far call:
; call _extFcnC ; go away you b***!
M_FARCALL _extFcnC ; hello there???
What it does is as it push CS and IP[08] only with no extra 0 words and makes a JUMP to function address. Since IP can not be pushed onto stack directly, place a label after JMP to Function and that would be your return address pushed onto stack. After that it worked and each of A, B and C parameters were returned correctly from the C function.