News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

linking obj file generated by VC cl with linker16

Started by ggmasm32, September 20, 2015, 08:18:42 AM

Previous topic - Next topic

ggmasm32

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_conventions

This 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 eax


Here 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 0010
You 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  :eusa_dance: :eusa_dance: :eusa_dance::
(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:
    ENDM


and 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.

ggmasm32

#16
 AA