x86 Assembly/Interfacing with C stdlib and own libraries

Prerequisites: CDECL, X86_Assembly/GAS_Syntax.

On Linux / GCC / GAS Syntax edit

Using the C stdlib edit

If you use GCC for linking, it will link with the C library by default. This means that you can probably call printf without changing your build process at all.

On this platform, the CDECL is used. Note the order in which the arguments are pushed.

printf example edit

# 32-bit x86 GAS/AT&T Linux assembly
.data
floatformatstr: .string "double: %f\n"
# the string GNU Assembler directive will automatically append the null byte that the C library expects at the end of strings
formatstr: .string "eax =\n\tin decimal: %d\n\tin hex: %08x\necx:edx = testdouble = %08x %08x\n%s\n" 
testdouble: .double 3.75
#equal to:
#testdouble: .quad 0b0100000000001110000000000000000000000000000000000000000000000000
teststr: .string "hi\twikibooks\t!"
.text
.globl main
main:

#this snippet copies the double to the normal stack, by using the floating point stack since
#it has instructions for dealing with doubles. we don't change the double at all though, so it is a bit silly:
fldl testdouble # load double onto the floating point stack
subl $8, %esp # make room on normal stack for the double
fstpl (%esp) # write the double on the normal stack (where %esp points to). this and the previous step correspond to a "pushq", or a quad-word push (4*16 bits = 64 bits)

#this snippet could be used too, it has the same effect (but is longer!):
#movl $testdouble, %eax
#pushl 4(%eax) # the stack grows downwards, so the bytes 5-8 of the double need to be pushed first
#movl $testdouble, %eax
#pushl 0(%eax) # after pushing bytes 1-4 the bytes 1-8 of the double are all on the stack

pushl $floatformatstr # format string last, since it is the first argument for printf (see "man 3 printf")
call printf
addl $12, %esp # caller cleans the stack up in the CDECL convension! 12 since we pushed 3*4 bytes.
# first eight bytes were the double, last 4 bytes were the char* (string), that is, the address of the first character in the format string.

movl $0xdeadbeef, %eax
movl $testdouble, %esi
movl 0(%esi), %ecx
movl 4(%esi), %edx

# "eax =\n\tin decimal: %d\n\tin hex: %08x\necx:edx = testdouble = %08x %08x\n%s\n"
#                                                                pushing this ^^
pushl $teststr
# "eax =\n\tin decimal: %d\n\tin hex: %08x\necx:edx = testdouble = %08x %08x\n%s\n"
#                                                          pushing this ^^^^
pushl %ecx
# "eax =\n\tin decimal: %d\n\tin hex: %08x\necx:edx = testdouble = %08x %08x\n%s\n"
#                                                     pushing this ^^^^
pushl %edx
# "eax =\n\tin decimal: %d\n\tin hex: %08x\necx:edx = testdouble = %08x %08x\n%s\n"
#                        pushing this ^^^^
pushl %eax
# "eax =\n\tin decimal: %d\n\tin hex: %08x\necx:edx = testdouble = %08x %08x\n%s\n"
#          pushing this ^^
pushl %eax
# pushing the whole format string pointer, which printf will use to determine how much of the stack to read and print
pushl $formatstr
call printf
addl $24, %esp # 6 * 4 bytes = 24 bytes

pushl $0
call exit
addl $4, %esp # should never be reached, since exit never returns
Output edit
double: 3.750000
eax =
	in decimal: -559038737
	in hex: deadbeef
ecx:edx = testdouble = 400e0000 00000000
hi	wikibooks	!

Questions and exercises edit

  1. Try reading about the double-precision floating-point format and understand why this bit pattern produces the number 3.75.
    In double precision numbers, the exponent is 11 bits, which means the bias is 210-1. The formula would be (Python, note that the range excludes the ending number, that's why there's a +1):
    (-1)**0*2**(pow(2,11)-(pow(2,10)-1))*(sum([1./2**n for n in range(0,3+1)]))
    = 3.75
          ^     ^^^^^^^^   ^^^^^^^^^^^    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    sign bit    set bit    exp bias       mantissa (in this case only the first three bit are set. The 1 is implicit, therefore the range goes from 0.)
  2. What happens to the hex representation if you change the 10th last bit to 1?
    Answer: the second double word of the double would change to 00000200, the floating point representation would change, but the change wouldn't be visible with this printf call because it is too small.
  3. How does printf know how many stack elements to pop?
    Answer: printf doesn't pop, it only reads. The top of the stack is a pointer to the format string which it reads. When it sees a %f, it will read 8 bytes. If it sees a %d, it will read 4 bytes.