跳轉到內容

x86 彙編/與 C 標準庫和自定義庫的互動

來自 Wikibooks,開放世界中的開放書籍

先決條件: CDECL, X86_Assembly/GAS_Syntax.

在 Linux / GCC / GAS 語法中

[編輯 | 編輯原始碼]

使用 C 標準庫

[編輯 | 編輯原始碼]

如果您使用 GCC 進行連結,它將預設連結到 C 庫。這意味著您可能可以呼叫printf而無需更改您的構建過程。

在這個平臺上,使用 CDECL。請注意引數入棧的順序。

printf示例

[編輯 | 編輯原始碼]
# 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	!

問題和練習

[編輯 | 編輯原始碼]
  1. 嘗試閱讀有關 雙精度浮點格式 的內容,並瞭解為什麼這個位模式產生數字 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.)
  2. 如果將最後第 10 位更改為 1,十六進位制表示會發生什麼變化?
    答案:雙精度的第二個雙字將變為 00000200,浮點表示將發生變化,但這個變化在這個 printf 呼叫中不可見,因為它太小了。
  3. printf 如何知道要彈出多少個堆疊元素?
    答案:printf 不會彈出,它只是讀取。堆疊的頂部是指向格式字串的指標,它讀取該字串。當它看到一個%f時,它將讀取 8 個位元組。如果它看到一個%d,它將讀取 4 個位元組。
華夏公益教科書