接着 没有翻译完的继续进行翻译。命令行参数在Linux中也是安排在堆栈上。argc在最前面,接着是保存参数字符串的指针数组,该指针数组的最后是一个NULL指针,第三个是一个保存执行环境的指针数组。这些参数都可以在汇编中很容易的获得,参加汇编代码(args.s)。
- # args.s
- .include "defines.h"
-
.text
-
.globl _start
-
_start:
-
popl %ecx # argc,每次用write输出一个字符串参数。循环%ecx次(lewp: ... jmp lewp 构成大循环)
-
lewp: # 大循环的开始处
-
popl %ecx # argv,每一个字符串的开始地址保存在%ecx中,作为write函数的第2个参数
-
test %ecx, %ecx # argv的最后一个字符串指针是NULL,根据这个来判断是否要结束大循环
-
jz exit # argv为NULL,跳到执行推出的代码处。
-
-
movl %ecx, %ebx # 将字符串的地址赋值给%ebx,来用于计算字符串的长度
-
xorl %edx, %edx # 将%edx清零,来计算字符串的长度
-
strlen: # 小循环(strlen: ... jnz strlen 用于计算每一个字符串参数的长度)
-
movb (%ebx), %al # 每次一个字节,每一个字节%edx增加1
-
inc %edx # %edx是保存字符串的长度,是write函数的第3个参数
-
inc %ebx # %edx自增,移到下一个字符的位置处
-
test %al, %al # 当遇到字符结尾的'\0'时,结束小循环,从而得到字符串的长度
-
jnz strlen
-
movb $10, -1(%ebx) # ???
-
-
# write(1,argv[i],strlen(argv[i])); # 每次循环利用write输出一个字符串参数
-
movl $SYS_write, %eax # write函数的系统调用号
-
movl $STDOUT, %ebx # write函数的第1个参数
-
int $0x80
-
-
jmp lewp # 继续大循环
-
exit:
-
movl $SYS_exit, %eax # exit的系统调用号
-
xorl %ebx, %ebx # exit的参数0
-
int $0x80 # 利用中断调用exit(0)函数
-
-
ret
其中头文件defines.h的内容(都是一些系统调用号的宏定义)如下:
- SYS_exit = 1
-
SYS_fork = 2
-
SYS_write = 4
-
SYS_open = 5
-
SYS_close = 6
-
SYS_execve = 11
-
SYS_lseek = 19
-
SYS_dup2 = 63
-
SYS_mmap = 90
-
SYS_munmap = 91
-
SYS_socketcall = 102
-
SYS_socketcall_socket = 1
-
SYS_socketcall_bind = 2
-
SYS_socketcall_listen = 4
-
SYS_socketcall_accept = 5
-
-
SEEK_END = 2
-
PROT_READ = 1
-
MAP_SHARED = 1
-
-
AF_INET = 2
-
SOCK_STREAM = 1
-
IPPROTO_TCP = 6
-
-
STDOUT = 1
编译、运行及结果:
- digdeep@ubuntu:~/assembly$ as -o args.o args.s
-
digdeep@ubuntu:~/assembly$ ld -o args args.o
-
digdeep@ubuntu:~/assembly$ ./args
-
./args
-
digdeep@ubuntu:~/assembly$ ./args arg1 arg2 arg3
-
./args
-
arg1
-
arg2
-
arg3
本小节讲的GCC内联汇编仅仅是指x86平台下。其它的处理器会存在一些差异。
简单的内联汇编是十分直接的。它的简单形式如下:
__asm__("movl %esp,%eax"); // look familiar ?
or
__asm__("
movl $1,%eax // SYS_exit
xor %ebx,%ebx
int $0x80
"
);
我们能够通过指定“输入数据”、“输出数据”以及“要修改的寄存器”,来更有效的使用内联汇编编程。这三个部分 input/output/modify都是可选的。它们的格式如下:
__asm__("" : output : input : modify);
指定的输入和输出部分必须是由:严格的字符串后面跟着一个被括号包含的C表达式。另外在输出部分的前面必须有一个“=”等号,暗示他是输出部分。允许有多个input/output/modify,各个部分用','隔开,但是总数不能超过10个。字符串即可以包含寄存器的全面,也可以是简写的形式。
Abbrev Table |
Abbrev | Register |
a | %eax/%ax/%al |
b | %ebx/%bx/%bl |
c | %ecx/%cx/%cl |
d | %edx/%dx/%dl |
S | %esi/%si |
D | %edi/%di |
m | memory |
Example:
__asm__("test %%eax,%%eax", : /* no output */ : "a"(foo));
OR
__asm__("test %%eax,%%eax", : /* no output */ : "eax"(foo));
你也可以在__asm__的后面使用关键字__volatile__ :来阻止一个汇编指令被优化掉。You can also use the keyword __volatile__ after __asm__: "You can prevent an `asm' instruction from being deleted, moved significantly, or combined, by writing the keyword `volatile' after the `asm'."
(引自:the "Assembler Instructions with C Expression Operands" section in the gcc info files.)
- #include <stdio.h>
-
-
int main(void) {
-
int foo=10,bar=15;
-
-
__asm__ __volatile__ (
- "addl %%ebxx, %%eax"
-
: "=eax"(foo) // ouput
-
: "eax"(foo), "ebx"(bar) // input
-
: "eax" // modify
-
);
-
printf("foo+bar=%d\n", foo);
-
return 0;
-
}
也许你已经注意到寄存器的前缀变成了"%%",而不是"%"。在内联汇编中不强制使用"eax"、"ax"、"al",你可以简单的写成"a"。其它的寄存器也可以用它的简写形式。参见上面的简写表格。
- int main(void) {
-
long eax;
-
short bx;
-
char cl;
-
__asm__("nop;nop;nop"); // to separate inline asm from the rest of the code
-
__volatile__ __asm__("
-
test %0,%0
-
test %1,%1
-
test %2,%2"
-
: /* no outputs */
-
: "a"((long)eax), "b"((short)bx), "c"((char)cl)
-
);
-
__asm__("nop;nop;nop");
-
return 0;
-
}
注意:如果需要同时执行多条汇编语句,则应该用"\n\t"将各个语句分隔开!
编译、运行及结果:
- digdeep@ubuntu:~/assembly$ gcc -o inline2 inline2.c
- digdeep@ubuntu:~/assembly$ ./inline2
- digdeep@ubuntu:~/assembly$ echo #?
- digdeep@ubuntu:~/assembly$
利用gdb调试:
digdeep@ubuntu:~/assembly$ gdb ./inline2
- GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2
- Copyright (C) 2010 Free Software Foundation, Inc.
- License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
- This is free software: you are free to change and redistribute it.
- There is NO WARRANTY, to the extent permitted by law. Type "show copying"
- and "show warranty" for details.
- This GDB was configured as "i686-linux-gnu".
- For bug reporting instructions, please see:
- <http://www.gnu.org/software/gdb/bugs/>...
- Reading symbols from /home/digdeep/assembly/inline2...(no debugging symbols found)...done.
- (gdb) disassemble main
- Dump of assembler code for function main:
- 0x08048394 <+0>: push %ebp
- 0x08048395 <+1>: mov %esp,%ebp
- 0x08048397 <+3>: push %ebx
- 0x08048398 <+4>: sub $0x10,%esp
- 0x0804839b <+7>: nop
- 0x0804839c <+8>: nop
- 0x0804839d <+9>: nop
- 0x0804839e <+10>: mov -0xc(%ebp),%eax
- 0x080483a1 <+13>: movzwl -0x8(%ebp),%edx
- 0x080483a5 <+17>: movzbl -0x5(%ebp),%ecx
- 0x080483a9 <+21>: mov %edx,%ebx
- 0x080483ab <+23>: test %eax,%eax
- 0x080483ad <+25>: test %bx,%bx
- 0x080483b0 <+28>: test %cl,%cl
- 0x080483b2 <+30>: nop
- 0x080483b3 <+31>: nop
- 0x080483b4 <+32>: nop
- 0x080483b5 <+33>: mov $0x0,%eax
- 0x080483ba <+38>: add $0x10,%esp
- 0x080483bd <+41>: pop %ebx
- 0x080483be <+42>: pop %ebp
- 0x080483bf <+43>: ret
- End of assembler dump.
- (gdb)
就像你看到的,从内联汇编生成的代码,将变量的值加载到寄存器中,因为这些寄存器放在了内联汇编的“输入部分”。编译器会自动检测到操作数的大小,所以编译器可以用它们的别名%0, %1, %2来代表。
寄存器的别名也可以被用到操作数的地方,但我们在input/output中不能指定超过10个的寄存器。
另外,我们用"q"来让编译器在a,b,c,d中自己来选择哪个寄存器。但是,当寄存器被修改之后,因为我们不知到哪个寄存器被选中,所以我们也不能在modify修改部分指定哪个寄存器。但是我们可以用寄存器别名的数字来指定。(注:其实新版的gcc已经不需要指定修改部分!!)
- #include <stdio.h>
- int main(void) {
- long eax=1,ebx=2;
- __asm__ __volatile__ ("add %0,%2"
- : "=b"((long)ebx) // %ebx作为输出
- : "a"((long)eax), "q"(ebx) // %eax作为变量eax的输入,变量ebx任选一个寄存器作为输入
- //: "1"
- );
- printf("ebx=%lx\n", ebx);
- return 0;
- }
编译、运行结果:
- digdeep@ubuntu:~/assembly$ gcc -o inline3 inline3.c
- digdeep@ubuntu:~/assembly$ ./inline3
- ebx=f19ff4
- digdeep@ubuntu:~/assembly$ ./inline3
- ebx=9cbff4
- digdeep@ubuntu:~/assembly$ ./inline3
- ebx=fb9ff4
- digdeep@ubuntu:~/assembly$
这里很奇怪:打印的结果不是我们所希望的!?每次都在变化,但是最后3位总是ff4.
Example Code: linux-asm.rar
阅读(1950) | 评论(0) | 转发(0) |