作者:陳曦
日期: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ǐng)注明出處
Q: 舉個(gè)例子吧。
A: 下面的代碼的目標(biāo)是計(jì)算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;
- }
運(yùn)行結(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)試運(yùn)行到assemble_func函數(shù)的開(kāi)始時(shí),使用disassemble命令得到的數(shù)據(jù)。注意第五行左側(cè)的箭頭符號(hào)是調(diào)試狀態(tài)顯示正在運(yùn)行的行數(shù)。
Q: 上面的匯編是內(nèi)嵌到c代碼中的,單獨(dú)完全的匯編代碼,如何實(shí)現(xiàn)hello world的功能?
A: 從本質(zhì)上說(shuō),只用匯編的形式需要對(duì)于底層更了解,c代碼從編譯的角度來(lái)說(shuō)和匯編沒(méi)什么區(qū)別,只是寫(xiě)的格式以及調(diào)用的東西看起來(lái)不一致罷了。
如下,是實(shí)現(xiàn)標(biāo)準(zhǔn)控制臺(tái)輸出功能的代碼:
- .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: 當(dāng)然可以,不過(guò)這個(gè)文件顯然不需要預(yù)處理了,它已經(jīng)是匯編格式了,不需要單純狹義的編譯過(guò)程了,只需要從匯編過(guò)程開(kāi)始了。
它可以直接生成目標(biāo)文件hello.o
Q: 接下來(lái)做什么?可以直接執(zhí)行它嗎?
A: 試試。
此時(shí),給hello.o添加可執(zhí)行權(quán)限再執(zhí)行:
Q: 這是為什么?
A: 繼續(xù)觀察hello.o文件的屬性。
可以看出,它還不是可執(zhí)行文件。其實(shí)很簡(jiǎn)單,hello.o只是目標(biāo)文件,并沒(méi)有鏈接成可執(zhí)行文件。
Q: 這又是為什么?沒(méi)有找到入口符號(hào)_start, ld默認(rèn)的入口符號(hào)是_start?
A: 是的。在代碼中使用的是_main, 所以應(yīng)該讓鏈接器明白,入口符號(hào)是_main.
Q: 現(xiàn)在應(yīng)該可以運(yùn)行了吧。運(yùn)行一下:
Hello,world是輸出了,為什么后面會(huì)出現(xiàn)段錯(cuò)誤呢?
A: 我們首先看看上面的運(yùn)行返回了什么。
返回值為139,它代表什么?
Q: 從系統(tǒng)的errno.h頭文件以及相關(guān)文件中查找,得到所有系統(tǒng)錯(cuò)誤碼:
/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文件:
就是沒(méi)有找到139.
A: 看來(lái),系統(tǒng)已經(jīng)發(fā)生一些詭異的情況,錯(cuò)誤碼已經(jīng)不正確了。為了確定139錯(cuò)誤碼確實(shí)不存在,我們?cè)?usr/include目錄下遞歸搜索139這個(gè)字符。
結(jié)果比較長(zhǎng),這里不列出來(lái)來(lái)。依然沒(méi)有能找到系統(tǒng)對(duì)應(yīng)的139錯(cuò)誤定義。
那么,我們來(lái)看看系統(tǒng)日志吧,到底哪里可能有問(wèn)題。
Q: 使用如下命令得到了錯(cuò)誤信息:
最后的地方確實(shí)看到hello應(yīng)用程序運(yùn)行錯(cuò)誤的系統(tǒng)日志。應(yīng)該是指針訪問(wèn)出錯(cuò)。原因是否是匯編代碼大最后沒(méi)有恰當(dāng)?shù)卦O(shè)置堆棧寄存器等寄存器的值呢?
A: 在這里,很有可能。為了更容易看出問(wèn)題可能在哪里,寫(xiě)一個(gè)類似功能的c代碼,得到它的匯編代碼,和上面的匯編代碼進(jìn)行比較。
Q: 寫(xiě)了如下的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代碼確實(shí)有不一樣。這里,開(kāi)始執(zhí)行時(shí)對(duì)ebp, esp進(jìn)行了處理,最后使用了leave和ret命令。就是它們引起的嗎?
A: 不過(guò)在實(shí)際中,不管是加入pushl %ebp之類代碼,還是加入leave, ret指令,最終執(zhí)行依然是段錯(cuò)誤。這個(gè)地方筆者一直沒(méi)明白,如果有誰(shuí)知道的,希望能不吝賜教。不過(guò),可以調(diào)用exit系統(tǒng)調(diào)用實(shí)現(xiàn)結(jié)束應(yīng)用程序,這樣就不會(huì)出現(xiàn)段錯(cuò)誤。如下:
- .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
運(yùn)行結(jié)果:
Q: 進(jìn)行0x80軟中斷進(jìn)行系統(tǒng)調(diào)用,參數(shù)在哪里保存,就在上面寫(xiě)的寄存器里面嗎?
A: 是的。linux下,功能號(hào)和返回值在eax中保存,參數(shù)一般在5個(gè)以下,就按照ebx, ecx, edx, esi, edi來(lái)傳遞,如果參數(shù)過(guò)多,就會(huì)使用堆棧??梢钥吹缴厦鎯纱蜗到y(tǒng)調(diào)用,均是在使用ebx, ecx, edx這些寄存器。
Q: 4號(hào)系統(tǒng)調(diào)用是什么?在哪里能知道?
A: 可以在/usr/include/asm/unistd_32.h或者/usr/include/asm/unistd_64.h中看到平臺(tái)所有系統(tǒng)調(diào)用,下面為unistd_32.h文件中開(kāi)始一部分:
- #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號(hào)系統(tǒng)調(diào)用為exit, 4號(hào)為write, 正是上面代碼使用的。
Q: 匯編如何調(diào)用c庫(kù)函數(shù)?
A: 使用call指令,不過(guò)調(diào)用前要傳好參數(shù)。如下代碼,調(diào)用c庫(kù)printf函數(shù):
- .section .rodata
- str:
- .ascii "Hello,world.\n"
-
- .section .text
- .globl main
- main:
-
- pushl $str
- call printf
-
- pushl $0
- call exit
保存為printf.s, 編譯:
運(yùn)行:
Q: 可以使用as, ld來(lái)匯編以及鏈接嗎?
A: 可以的。不過(guò)需要注意,因?yàn)樗褂胏庫(kù),需要指定鏈接c庫(kù): -lc;
Q: 乘法運(yùn)算mul后面只跟著一個(gè)數(shù),另一個(gè)數(shù)存哪里?
A: 另一個(gè)數(shù)存儲(chǔ)在al, ax或者eax寄存器中,這取決于使用的是mulb, mulw還是mull指令。結(jié)果將按照高位到地位的順序保存在dx和ax中。
同理,除法運(yùn)算div后面也只跟一個(gè)除數(shù),被除數(shù)保存在ax, dx:ax或者edx:eax中。除數(shù)的最大長(zhǎng)度只能是被除數(shù)的一半。商和余數(shù)將根據(jù)被除數(shù)占用大小來(lái)確定:
如果被除數(shù)在ax中,商在al, 余數(shù)在ah; 如果被除數(shù)在eax中,商在ax, 余數(shù)在dx; 如果被除數(shù)在edx:eax中,商在eax, 余數(shù)在edx.
如下是測(cè)試代碼:
- #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: 對(duì)于數(shù)據(jù)比較指令cmp,它是如何配合jmp相關(guān)的指令?
A: cmp指令將進(jìn)行兩個(gè)數(shù)據(jù)的差計(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會(huì)成立,輸出如下:
Q: 對(duì)于某些時(shí)候,加法可能導(dǎo)致溢出,如何判斷出來(lái)?
A: CPU內(nèi)部有一個(gè)寄存器,它內(nèi)部會(huì)保存溢出標(biāo)志位OF, 可以通過(guò)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;
- }
運(yùn)行結(jié)果:
- "overflow flag is set..."
Q: 對(duì)于溢出,到底應(yīng)該判斷?
A: 以加法舉例,如果兩個(gè)相同符號(hào)的數(shù)相加得到的結(jié)果符號(hào)相反,那么一定溢出了。
Q: OF和CF標(biāo)志位有什么區(qū)別?
A: CF代表進(jìn)位標(biāo)志。進(jìn)位不一定是溢出,比如有符號(hào)整形最小值加1,雖然進(jìn)位,但是沒(méi)溢出。因?yàn)橛?jì)算機(jī)補(bǔ)碼的理論允許進(jìn)位,但是結(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;
- }
運(yùn)行結(jié)果:
當(dāng)然,我們可以用jo來(lái)測(cè)試上面的加法是否溢出。
- #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é)果:
它什么也沒(méi)輸出,這就意味著OF沒(méi)有被置位。
作者:陳曦
日期: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ǐng)注明出處