作者:陳曦
日期:2012-6-8 10:50:13
環(huán)境:[Ubuntu 11.04 Intel-based x64 gcc4.5.2 CodeBlocks10.05 AT&T匯編 Intel匯編]
轉(zhuǎn)載請注明出處
Q: 舉個例子吧。
A: 下面的代碼的目標是計算1+2的值,最后放到變量temp中,并輸出:
- #include <stdio.h>
- #include <string.h>
-
- #define PRINT_D(longValue) printf(#longValue" is %ld\n", ((long)longValue));
- #define PRINT_STR(str) printf(#str" is %s\n", (str));
-
-
- static void assemble_func()
- {
- int temp;
- __asm__("mov $1, %eax");
- __asm__("mov $2, %ebx");
- __asm__("add %ebx, %eax");
- __asm__("mov %%eax, %0":"=r"(temp));
- PRINT_D(temp)
- }
-
- int main()
- {
- assemble_func();
- return 0;
- }
運行結(jié)果:
Q: assemble_func函數(shù)的匯編代碼形式是什么?
A:
- 0x08048404 <+0>: push ebp
- 0x08048405 <+1>: mov ebp,esp
- 0x08048407 <+3>: push ebx
- 0x08048408 <+4>: sub esp,0x24
- => 0x0804840b <+7>: mov eax,0x1
- 0x08048410 <+12>: mov ebx,0x2
- 0x08048415 <+17>: add eax,ebx
- 0x08048417 <+19>: mov ebx,eax
- 0x08048419 <+21>: mov DWORD PTR [ebp-0xc],ebx
- 0x0804841c <+24>: mov eax,0x8048510
- 0x08048421 <+29>: mov edx,DWORD PTR [ebp-0xc]
- 0x08048424 <+32>: mov DWORD PTR [esp+0x4],edx
- 0x08048428 <+36>: mov DWORD PTR [esp],eax
- 0x0804842b <+39>: call 0x8048340 <printf@plt>
- 0x08048430 <+44>: add esp,0x24
- 0x08048433 <+47>: pop ebx
- 0x08048434 <+48>: pop ebp
- 0x08048435 <+49>: ret
上面的匯編是在調(diào)試運行到assemble_func函數(shù)的開始時,使用disassemble命令得到的數(shù)據(jù)。注意第五行左側(cè)的箭頭符號是調(diào)試狀態(tài)顯示正在運行的行數(shù)。
Q: 上面的匯編是內(nèi)嵌到c代碼中的,單獨完全的匯編代碼,如何實現(xiàn)hello world的功能?
A: 從本質(zhì)上說,只用匯編的形式需要對于底層更了解,c代碼從編譯的角度來說和匯編沒什么區(qū)別,只是寫的格式以及調(diào)用的東西看起來不一致罷了。
如下,是實現(xiàn)標準控制臺輸出功能的代碼:
- .section .rodata
- str:
- .ascii "Hello,world.\n"
-
- .section .text
- .globl _main
- _main:
- movl $4, %eax # the number of system call
- movl $1, %ebx # file descriptor, 1 means stdout
- movl $str, %ecx # string address
- movl $13, %edx # string length
- int $0x80
保存為hello.s.
Q: 如何編譯它,使用gcc嗎?
A: 當然可以,不過這個文件顯然不需要預處理了,它已經(jīng)是匯編格式了,不需要單純狹義的編譯過程了,只需要從匯編過程開始了。

它可以直接生成目標文件hello.o
Q: 接下來做什么?可以直接執(zhí)行它嗎?
A: 試試。

此時,給hello.o添加可執(zhí)行權限再執(zhí)行:

Q: 這是為什么?
A: 繼續(xù)觀察hello.o文件的屬性。

可以看出,它還不是可執(zhí)行文件。其實很簡單,hello.o只是目標文件,并沒有鏈接成可執(zhí)行文件。

Q: 這又是為什么?沒有找到入口符號_start, ld默認的入口符號是_start?
A: 是的。在代碼中使用的是_main, 所以應該讓鏈接器明白,入口符號是_main.

Q: 現(xiàn)在應該可以運行了吧。運行一下:

Hello,world是輸出了,為什么后面會出現(xiàn)段錯誤呢?
A: 我們首先看看上面的運行返回了什么。

返回值為139,它代表什么?
Q: 從系統(tǒng)的errno.h頭文件以及相關文件中查找,得到所有系統(tǒng)錯誤碼:
/usr/include/asm-generic/errno-base.h文件:
- #ifndef _ASM_GENERIC_ERRNO_BASE_H
- #define _ASM_GENERIC_ERRNO_BASE_H
-
- #define EPERM 1 /* Operation not permitted */
- #define ENOENT 2 /* No such file or directory */
- #define ESRCH 3 /* No such process */
- #define EINTR 4 /* Interrupted system call */
- #define EIO 5 /* I/O error */
- #define ENXIO 6 /* No such device or address */
- #define E2BIG 7 /* Argument list too long */
- #define ENOEXEC 8 /* Exec format error */
- #define EBADF 9 /* Bad file number */
- #define ECHILD 10 /* No child processes */
- #define EAGAIN 11 /* Try again */
- #define ENOMEM 12 /* Out of memory */
- #define EACCES 13 /* Permission denied */
- #define EFAULT 14 /* Bad address */
- #define ENOTBLK 15 /* Block device required */
- #define EBUSY 16 /* Device or resource busy */
- #define EEXIST 17 /* File exists */
- #define EXDEV 18 /* Cross-device link */
- #define ENODEV 19 /* No such device */
- #define ENOTDIR 20 /* Not a directory */
- #define EISDIR 21 /* Is a directory */
- #define EINVAL 22 /* Invalid argument */
- #define ENFILE 23 /* File table overflow */
- #define EMFILE 24 /* Too many open files */
- #define ENOTTY 25 /* Not a typewriter */
- #define ETXTBSY 26 /* Text file busy */
- #define EFBIG 27 /* File too large */
- #define ENOSPC 28 /* No space left on device */
- #define ESPIPE 29 /* Illegal seek */
- #define EROFS 30 /* Read-only file system */
- #define EMLINK 31 /* Too many links */
- #define EPIPE 32 /* Broken pipe */
- #define EDOM 33 /* Math argument out of domain of func */
- #define ERANGE 34 /* Math result not representable */
-
- #endif
/usr/include/asm-generic/errno.h文件:
就是沒有找到139.
A: 看來,系統(tǒng)已經(jīng)發(fā)生一些詭異的情況,錯誤碼已經(jīng)不正確了。為了確定139錯誤碼確實不存在,我們在/usr/include目錄下遞歸搜索139這個字符。
結(jié)果比較長,這里不列出來來。依然沒有能找到系統(tǒng)對應的139錯誤定義。
那么,我們來看看系統(tǒng)日志吧,到底哪里可能有問題。
Q: 使用如下命令得到了錯誤信息:

最后的地方確實看到hello應用程序運行錯誤的系統(tǒng)日志。應該是指針訪問出錯。原因是否是匯編代碼大最后沒有恰當?shù)卦O置堆棧寄存器等寄存器的值呢?
A: 在這里,很有可能。為了更容易看出問題可能在哪里,寫一個類似功能的c代碼,得到它的匯編代碼,和上面的匯編代碼進行比較。
Q: 寫了如下的hello_1.c代碼如下:
- #include <stdio.h>
-
- int main()
- {
- printf("Hello,world!\n");
- return 0;
- }
查看它的匯編代碼:
- .file "hello_1.c"
- .section .rodata
- .LC0:
- .string "Hello,world!"
- .text
- .globl main
- .type main, @function
- main:
- pushl %ebp
- movl %esp, %ebp
- andl $-16, %esp
- subl $16, %esp
- movl $.LC0, (%esp)
- call puts
- movl $0, %eax
- leave
- ret
- .size main, .-main
- .ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
- .section .note.GNU-stack,"",@progbits
果然,和hello.s代碼確實有不一樣。這里,開始執(zhí)行時對ebp, esp進行了處理,最后使用了leave和ret命令。就是它們引起的嗎?
A: 不過在實際中,不管是加入pushl %ebp之類代碼,還是加入leave, ret指令,最終執(zhí)行依然是段錯誤。這個地方筆者一直沒明白,如果有誰知道的,希望能不吝賜教。不過,可以調(diào)用exit系統(tǒng)調(diào)用實現(xiàn)結(jié)束應用程序,這樣就不會出現(xiàn)段錯誤。如下:
- .section .rodata
- str:
- .ascii "Hello,world.\n"
-
- .section .text
- .globl _main
- _main:
-
- movl $4, %eax # the number of system call
- movl $1, %ebx # file descriptor, 1 means stdout
- movl $str, %ecx # string address
- movl $13, %edx # string length
- int $0x80
-
- movl $1, %eax
- movl $0, %ebx
- int $0x80
運行結(jié)果:

Q: 進行0x80軟中斷進行系統(tǒng)調(diào)用,參數(shù)在哪里保存,就在上面寫的寄存器里面嗎?
A: 是的。linux下,功能號和返回值在eax中保存,參數(shù)一般在5個以下,就按照ebx, ecx, edx, esi, edi來傳遞,如果參數(shù)過多,就會使用堆棧??梢钥吹缴厦鎯纱蜗到y(tǒng)調(diào)用,均是在使用ebx, ecx, edx這些寄存器。
Q: 4號系統(tǒng)調(diào)用是什么?在哪里能知道?
A: 可以在/usr/include/asm/unistd_32.h或者/usr/include/asm/unistd_64.h中看到平臺所有系統(tǒng)調(diào)用,下面為unistd_32.h文件中開始一部分:
- #define __NR_restart_syscall 0
- #define __NR_exit 1
- #define __NR_fork 2
- #define __NR_read 3
- #define __NR_write 4
- #define __NR_open 5
- #define __NR_close 6
- #define __NR_waitpid 7
- #define __NR_creat 8
- #define __NR_link 9
- #define __NR_unlink 10
- #define __NR_execve 11
- #define __NR_chdir 12
- #define __NR_time 13
- #define __NR_mknod 14
- #define __NR_chmod 15
- #define __NR_lchown 16
- #define __NR_break 17
可以看到,1號系統(tǒng)調(diào)用為exit, 4號為write, 正是上面代碼使用的。
Q: 匯編如何調(diào)用c庫函數(shù)?
A: 使用call指令,不過調(diào)用前要傳好參數(shù)。如下代碼,調(diào)用c庫printf函數(shù):
- .section .rodata
- str:
- .ascii "Hello,world.\n"
-
- .section .text
- .globl main
- main:
-
- pushl $str
- call printf
-
- pushl $0
- call exit
保存為printf.s, 編譯:

運行:

Q: 可以使用as, ld來匯編以及鏈接嗎?
A: 可以的。不過需要注意,因為它使用c庫,需要指定鏈接c庫: -lc;
Q: 乘法運算mul后面只跟著一個數(shù),另一個數(shù)存哪里?
A: 另一個數(shù)存儲在al, ax或者eax寄存器中,這取決于使用的是mulb, mulw還是mull指令。結(jié)果將按照高位到地位的順序保存在dx和ax中。
同理,除法運算div后面也只跟一個除數(shù),被除數(shù)保存在ax, dx:ax或者edx:eax中。除數(shù)的最大長度只能是被除數(shù)的一半。商和余數(shù)將根據(jù)被除數(shù)占用大小來確定:
如果被除數(shù)在ax中,商在al, 余數(shù)在ah; 如果被除數(shù)在eax中,商在ax, 余數(shù)在dx; 如果被除數(shù)在edx:eax中,商在eax, 余數(shù)在edx.
如下是測試代碼:
- #include <stdio.h>
- #include <string.h>
-
- #define PRINT_D(longValue) printf(#longValue" is %ld\n", ((long)longValue));
- #define PRINT_STR(str) printf(#str" is %s\n", (str));
-
-
- static void assemble_func()
- {
- int result_high, result_low;
- short result, remainder;
-
-
- __asm__("mov $10, %eax");
- __asm__("mov $10, %ebx");
- __asm__("mull %ebx");
- __asm__("mov %%edx, %0":"=r"(result_high));
- __asm__("mov %%eax, %0":"=r"(result_low));
- PRINT_D(result_high)
- PRINT_D(result_low)
-
-
- __asm__("mov $0, %dx");
- __asm__("mov $100, %ax");
- __asm__("mov $9, %bx");
- __asm__("div %bx");
- __asm__("movw %%ax, %0":"=r"(result));
- __asm__("movw %%dx, %0":"=r"(remainder));
- PRINT_D(result)
- PRINT_D(remainder)
- }
-
- int main()
- {
- assemble_func();
- return 0;
- }
輸出結(jié)果:
- result_high is 0
- result_low is 100
- result is 11
- remainder is 1
Q: 對于數(shù)據(jù)比較指令cmp,它是如何配合jmp相關的指令?
A: cmp指令將進行兩個數(shù)據(jù)的差計算,如果得到的是0,jz成立; 如果不是0, jnz成立。如下例子:
- #include <stdio.h>
- #include <string.h>
-
- #define PRINT_D(longValue) printf(#longValue" is %ld\n", ((long)longValue));
- #define PRINT_STR(str) printf(#str" is %s\n", (str));
- #define PRINT(str) printf(#str"\n");
-
-
- static void assemble_func()
- {
- __asm__("mov $10, %eax");
- __asm__("cmp $10, %eax ");
- __asm__("jz end");
- PRINT("below jz")
- __asm__("end:");
- PRINT("the end")
-
- }
-
- int main()
- {
- assemble_func();
- return 0;
- }
顯然,jz會成立,輸出如下:
Q: 對于某些時候,加法可能導致溢出,如何判斷出來?
A: CPU內(nèi)部有一個寄存器,它內(nèi)部會保存溢出標志位OF, 可以通過jo或者jno判斷。
- #include <stdio.h>
- #include <string.h>
-
- #define PRINT_D(longValue) printf(#longValue" is %ld\n", ((long)longValue));
- #define PRINT_STR(str) printf(#str" is %s\n", (str));
- #define PRINT(str) printf(#str"\n");
-
-
- static void assemble_func()
- {
- __asm__("movw $0x7FFF, %ax");
- __asm__("movw $0x7FFF, %bx");
- __asm__("addw %bx, %ax");
-
- __asm__("jo overflow_set");
-
- __asm__("movl $1, %eax");
- __asm__("movl $0, %ebx");
- __asm__("int $0x80");
-
- __asm__("overflow_set:");
- PRINT("overflow flag is set...")
- }
-
- int main()
- {
- assemble_func();
- return 0;
- }
運行結(jié)果:
- "overflow flag is set..."
Q: 對于溢出,到底應該判斷?
A: 以加法舉例,如果兩個相同符號的數(shù)相加得到的結(jié)果符號相反,那么一定溢出了。
Q: OF和CF標志位有什么區(qū)別?
A: CF代表進位標志。進位不一定是溢出,比如有符號整形最小值加1,雖然進位,但是沒溢出。因為計算機補碼的理論允許進位,但是結(jié)果卻正確。
- #include <stdio.h>
- #include <string.h>
-
- #define PRINT_D(longValue) printf(#longValue" is %ld\n", ((long)longValue));
- #define PRINT_STR(str) printf(#str" is %s\n", (str));
- #define PRINT(str) printf(#str"\n");
-
-
- static void assemble_func()
- {
- __asm__("movw $0xFFFF, %ax");
- __asm__("movw $0x1, %bx");
- __asm__("addw %bx, %ax");
-
- __asm__("je carry_set");
-
- __asm__("movl $1, %eax");
- __asm__("movl $0, %ebx");
- __asm__("int $0x80");
-
- __asm__("carry_set:");
- PRINT("carry flag is set...")
- }
-
- int main()
- {
- assemble_func();
- return 0;
- }
運行結(jié)果:
當然,我們可以用jo來測試上面的加法是否溢出。
- #include <stdio.h>
- #include <string.h>
-
- #define PRINT_D(longValue) printf(#longValue" is %ld\n", ((long)longValue));
- #define PRINT_STR(str) printf(#str" is %s\n", (str));
- #define PRINT(str) printf(#str"\n");
-
-
- static void assemble_func()
- {
- __asm__("movw $0xFFFF, %ax");
- __asm__("movw $0x1, %bx");
- __asm__("addw %bx, %ax");
-
- __asm__("jo overflow_set");
-
- __asm__("movl $1, %eax");
- __asm__("movl $0, %ebx");
- __asm__("int $0x80");
-
- __asm__("overflow_set:");
- PRINT("overflow flag is set...")
- }
-
- int main()
- {
- assemble_func();
- return 0;
- }
執(zhí)行結(jié)果:
它什么也沒輸出,這就意味著OF沒有被置位。
作者:陳曦
日期:2012-6-8 10:50:13
環(huán)境:[Ubuntu 11.04 Intel-based x64 gcc4.5.2 CodeBlocks10.05 AT&T匯編 Intel匯編]
轉(zhuǎn)載請注明出處