x86 汇编/与 C 标准库和自定义库的交互
外观
< X86 汇编
先决条件: CDECL, X86_Assembly/GAS_Syntax.
如果您使用 GCC 进行链接,它将默认链接到 C 库。这意味着您可能可以调用printf而无需更改您的构建过程。
在这个平台上,使用 CDECL。请注意参数入栈的顺序。
# 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
double: 3.750000 eax = in decimal: -559038737 in hex: deadbeef ecx:edx = testdouble = 400e0000 00000000 hi wikibooks !
- 尝试阅读有关 双精度浮点格式 的内容,并了解为什么这个位模式产生数字 3.75。
- 在双精度数字中,指数是 11 位,这意味着偏差是 210-1。公式将是(Python,请注意范围不包括结束数字,这就是为什么有一个 +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.)
- 在双精度数字中,指数是 11 位,这意味着偏差是 210-1。公式将是(Python,请注意范围不包括结束数字,这就是为什么有一个 +1)
- 如果将最后第 10 位更改为 1,十六进制表示会发生什么变化?
- 答案:双精度的第二个双字将变为 00000200,浮点表示将发生变化,但这个变化在这个 printf 调用中不可见,因为它太小了。
- printf 如何知道要弹出多少个堆栈元素?
- 答案:printf 不会弹出,它只是读取。堆栈的顶部是指向格式字符串的指针,它读取该字符串。当它看到一个%f时,它将读取 8 个字节。如果它看到一个%d,它将读取 4 个字节。