professional assembly langage 读书笔记

简单的汇编程序:
# cpuid.s : extract the vender ID of cpu .section .data output: .ascii "the processor vender ID is 'xxxxxxxxxxxx'/n" .section .text .globl _start _start: nop movl $0, %eax cpuid movl $output, %edi movl %ebx, 28 (%edi) movl %edx, 32 (%edi) movl %ecx, 36 (%edi) movl $4, %eax movl $1, %ebx movl $output, %ecx movl $42, %edx int $0x80 movl $1, %eax movl $0, %ebx int $0x80

 

.section命令定义了数据段。output生命了一个标签为output的字符串。.ascii命令表示使用ascii字符声明字符串。
如果使用.bss段,则可以在.data段后定义。
接着定义了文本段(代码段)。.globl
命令指明后面的标签是外部程序的入口(本例是_start),接着_start是本程序的入口点(GNU汇编器默认的)。
nop是一个空指令,什么也做。(在调试时,有用)

本程序演示的是使用cpuid指令获取cpu厂商。
cpuid指令的要求是:
eax的值决定cpuid获取cpu的什么信息。本例中是eax=0,声称cpu厂商ID。获取后的值
依次放在ebx,edx,ecx,以小尾数(little endian)的形式。

$在上下文中有 三种意思:
1.用在立即数前面。
2.用在内存标记前面,表示标记的地址。
3.在gdb调试时,是用在寄存器前面,

再接着程序看看, movl $output, %edi
,把output标记的地址放在edi寄存器中,edi寄存器是处理字符串的寄存器(destination)。
然后分别把ebx,edx,ecx,的指放在output所标记的字符串的占位符中(xxxxxxxxxxxx)。
括号()表示,edi寄存器存储的是内存地址,此时是对edi寄存器所存储的地址所在的内存空间
进行处理(间接寻址)。

既然都把结果放在了output所标记的字符串里,下面就要显示信息了。本例使用的是
int $0x80系统(软)中断。0x80的要求是:
eax包含系统调用指,本例是4号系统调用。
ebx表示文件描述符,1是标准输出。
ecx是所要显示字符串的首地址。
edx是要输出的字符串的长度。

最后要退出程序,返回os。还是使用int $0x80软中断。
系统调用是1号。
ebx存储退出码,程序执行后可以在shell中,使用echo $?命令查看退出码。

程序看完了,开始汇编(动词),连接。
$ as cpuid.s -o cpuid.o $ ld cpuid.o -o cpuid $./cpuid the processor vender ID is 'GenuineIntel' $echo $? 0

 

我们来搞一下,把退出码改为100

wolf@debian:~/programming/assembly$ ./cpuid the processor vender ID is 'GenuineIntel' wolf@debian:~/programming/assembly$ echo $? 100

 

确实如此。还是改过来吧,0通常表示程序正常退出。

上面我们使用的是gnu,binutils软件包里的东西,
如果我们想使用gcc来编译呢?
只需把程序入口地址改为main即可,如下

 

.section .data output: .ascii "the processor vender ID is 'xxxxxxxxxxxx'/n" .section .text .globl main #here main: #and here ,we change it to main from _start label nop movl $0, %eax cpuid movl $output, %edi movl %ebx, 28 (%edi) movl %edx, 32 (%edi) movl %ecx, 36 (%edi) movl $4, %eax movl $1, %ebx movl $output, %ecx movl $42, %edx int $0x80 movl $1, %eax movl $0, %ebx int $0x80 输入命令: wolf@debian:~/programming/assembly$ gcc cpuid.s -o cpuid wolf@debian:~/programming/assembly$ ./cpuid the processor vender ID is 'GenuineIntel'

下面开始调试,使用gdb。
使用gas在汇编阶段,要指定选项-gstabs,才能把源码的很多信息汇编进可执行代码,这样才能调试。
然后再ld。
**************当然如果使用gcc编译的话,也可以在编译阶段用-g选项。 但我调试时,出现了错误:
(gdb) break *_start + 1 Breakpoint 1 at 0x80482e1 (gdb) run Starting program: /home/wolf/programming/assembly/cpuid Program received signal SIGSEGV, Segmentation fault. 0x080482e2 in _start () (gdb)

 

本例以-gstabs调试为例:

wolf@debian:~/programming/assembly$ gdb cpuid GNU gdb (GDB) 7.0.1-debian Copyright (C) 2009 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 "i486-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /home/wolf/programming/assembly/cpuid...done. (gdb) list 1 .section .data 2 output: 3 .ascii "the processor vender ID is 'xxxxxxxxxxxx'/n" 4 5 .section .text 6 7 .globl _start 8 _start: 9 nop 10 movl $0, %eax (gdb)

 

我们使用list列举了十行源码,list是用来查看函数的源码的(man gdb)。

再然后,我们设置断点,对汇编程序进行设置断点,有几个地方可以进行设置:
1.某个标记
2.某行
3.数值到达指定值
4.函数执行了指定次数
本例,我们就在_start标签处设置断点,上面已经给出了nop这个空指令,在调试时,如果
把断点设置成break *_start,就根本不能在断点处停下来。
(gdb) break *_start+1 Breakpoint 1 at 0x8048075: file cpuid.s, line 10. (gdb) run Starting program: /home/wolf/programming/assembly/cpuid Breakpoint 1, _start () at cpuid.s:10 10 movl $0, %eax Current language: auto The current source language is "auto; currently asm". (gdb) 在断点出停下来了,查看各个寄存器的值: (gdb) info registers eax 0x0 0 ecx 0x0 0 edx 0x0 0 ebx 0x0 0 esp 0xbffff4c0 0xbffff4c0 ebp 0x0 0x0 esi 0x0 0 edi 0x0 0 eip 0x8048075 0x8048075 <_start+1> eflags 0x292 [ AF SF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x0 0 (gdb) next 11 cpuid (gdb) info registers eax 0x0 0 ecx 0x0 0 edx 0x0 0 ebx 0x0 0 esp 0xbffff4c0 0xbffff4c0 ebp 0x0 0x0 esi 0x0 0 edi 0x0 0 eip 0x804807a 0x804807a <_start+6> eflags 0x292 [ AF SF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x0 0 (gdb) step 12 movl $output, %edi (gdb) info registers eax 0xa 10 ecx 0x6c65746e 1818588270 edx 0x49656e69 1231384169 ebx 0x756e6547 1970169159 esp 0xbffff4c0 0xbffff4c0 ebp 0x0 0x0 esi 0x0 0 edi 0x0 0 eip 0x804807c 0x804807c <_start+8> eflags 0x292 [ AF SF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x0 0

 

进行单步调试,并且再次查看所有寄存器的值。
(gdb) continue Continuing. the processor vender ID is 'GenuineIntel' Program exited normally.

 

恢复正常执行(接着执行)continue。

 在上面的过程中,我们使用info register来查看寄存器的值,还可以通过print来查看指定寄存器的值(print也可以查看变量的值无须取地址print i):
 print/d  以十进制显示
 print/t 以二进制显示
 print/x 以十六进制显示
(gdb) print/x $edi $2 = 0x80490ac (gdb) print/t $edi $3 = 1000000001001001000010101100 (gdb) print/d $edi $4 = 134516908

 

在指定寄存器的时候要使用$符。

如果要查看内存(变量)的内容,也可以使用x命令,格式为:
x/nyz
n为要显示大的字段数,
y为输出格式:
c  -->字符
d  -->十进制
x  -->十六进制

z为显示字段的长度(每隔xx显示):
b -->一个字节
h -->两个字节(十六位)
w -->四个字节(32位)

 

(gdb) x/42cb &output 0x80490ac <output>: 116 't' 104 'h' 101 'e' 32 ' ' 112 'p' 114 'r' 111 'o' 99 'c' 0x80490b4 <output+8>: 101 'e' 115 's' 115 's' 111 'o' 114 'r' 32 ' ' 118 'v' 101 'e' 0x80490bc <output+16>: 110 'n' 100 'd' 101 'e' 114 'r' 32 ' ' 73 'I' 68 'D' 32 ' ' 0x80490c4 <output+24>: 105 'i' 115 's' 32 ' ' 39 '/'' 120 'x' 120 'x' 120 'x' 120 'x' 0x80490cc <output+32>: 120 'x' 120 'x' 120 'x' 120 'x' 120 'x' 120 'x' 120 'x' 120 'x' 0x80490d4 <output+40>: 39 '/'' 10 '/n' (gdb) x/42ch &output 0x80490ac <output>: 116 't' 101 'e' 112 'p' 111 'o' 101 'e' 115 's' 114 'r' 118 'v' 0x80490bc <output+16>: 110 'n' 101 'e' 32 ' ' 68 'D' 105 'i' 32 ' ' 120 'x' 120 'x' 0x80490cc <output+32>: 120 'x' 120 'x' 120 'x' 120 'x' 39 '/'' Cannot access memory at address 0x80490d6 (gdb) x/42cw &output 0x80490ac <output>: 116 't' 112 'p' 101 'e' 114 'r' 0x80490bc <output+16>: 110 'n' 32 ' ' 105 'i' 120 'x' 0x80490cc <output+32>: 120 'x' 120 'x' Cannot access memory at address 0x80490d4

 

在查看内存内容的时候,需在内存标记前加&符号,表示内存地址。

最后,在汇编中连接C库函数,本例连接动态连接库。
#cpuid2.s .section .data output: .asciz "The processor Vender ID is '/%s'/n" .section .bss .lcomm buffer, 12 .section .text .globl _start _start: movl $0, %eax cpuid movl $buffer, %edi movl %ebx, (%edi) movl %edx, 4(%edi) movl %ecx, 8(%edi) pushl $buffer pushl $output call printf addl $8, %esp pushl $0 call exit

 

 

命令.asciz在字符串后面自动添加空字符(c风格的字符串),因为printf函数需要这样的字符串。
.bss段中,使用.lcomm命令,定义了12个字节的数据缓冲区。为了把参数传递给printf函数,
必须把参数压到栈里,使用pushl命令完成,call指令调用printf函数,addl指令把esp(栈顶指针)加8
也即清空了刚刚压入栈的内容。最后把0放入栈中,供exit函数调用。

下面开始汇编,连接:
wolf@debian:~/programming/assembly$ as -o cpuid2.o cpuid2.s
wolf@debian:~/programming/assembly$ ld -dynamic-linker /lib/ld-linux.so.2 -o cpuid2 /
> -lc cpuid.o
wolf@debian:~/programming/assembly$ ./cpuid2
the processor vender ID is 'GenuineIntel'
c语言的动态连接库是libc.so,在命令行中可以通过-lc来指定,(这是ld的规则,去掉lib和.so,剩下c)
,
    指定了动态库,那如何在运行时动态加载动态库?这就需要ld-linux.so.2的帮忙了,通过
使用-dynamic-linker选项指定加载动态连接库的程序ld-linux.so.2 。

当然也可以使用gcc来编译,只需要把_start标签改为main即可。gcc自动连接c函数库。
wolf@debian:~/programming/assembly$ gcc -o cpuid2 cpuid2.s wolf@debian:~/programming/assembly$ ./cpuid2 The processor Vender ID is 'GenuineIntel'


更正:

1.上面红色部分1,设置断点时,因为gcc是从main标签开始,所以设置的断点是break *main+1 ,这样就没有错误了.

你可能感兴趣的:(professional assembly langage 读书笔记)