文章参考书籍:
1、程序的组成(定义段和起始点)
汇编语言由定义好的段构成,三个常用段如下:
数据段、bss段、文本段
所有汇编语言都必须要有文本,这里是可执行程序声明指令码的地方。数据段和bss段是可选的。数据段声明带有初始值的数据元素。这些数据用作汇编语言程序中的变量。bss段声明使用零值初始的数据段,这些元素常用作汇编语言的缓冲区
1)定义段
bss段总是在文本段之前,数据段可以移动到文本段之后
2)定义 起始点
当汇编语言编译成可执行程序后,程序必须知道起始点
zhanglei@ubuntu:~/ass$ ld main
ld: warning: cannot find entry symbol _start; not setting start address
为了解决这个问题,_start 标签表示程序指令应该从这里开始,如果找不到这个标签,连接器就会试图找其他起点,但是对于复杂的程序并不能找到起点。
.globl 命令声明外部程序可以访问的标签。如果编写外部汇编语言或者c语言使用的一组工具,就应该使用global命令声明每个函数的段标签
2、创建简单程序
1)cpuid
cpuid是一条汇编语言指令,不容易从高级语言中执行他。
cpuid使用单一寄存器值作为输入,EAX寄存器(用于操作数和结果数的累加器)用于决定cpuid指令生成什么信息。根据EAX寄存器的值,cpuid指令在EBX、ECX、EDX寄存器中生成关于处理器的不同信息。
EBX 包含最低四个字节
EDX 包含中间四个字节
ECX 包含最高四个字节
范例程序获取寄存器值的信息,并按照易读的信息展示给用户。
2)范例程序
编写汇编程序
# cpuid.s extract the the processor vendor ID
.section .data
output:
.ascii "the processor vendor ID is 'xxxxxxxxxxxx'\n"
.section .text
.global _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
mov $1,%eax
mov $0,%ebx
int $0x80
生成目标文件
as -o cpuid.o cpuid.s
链接生成二进制
ld -o cpuid cpuid.o
执行二进制
zhanglei@ubuntu:~/ass$ ./cpuid
the processor vendor ID is 'GenuineIntel'
程序分析:
声明一个ascii码字符串
output:
.ascii "the processor vendor ID is 'xxxxxxxxxxxx'\n"
声明指令码段和开始标签
.section .text
.global _start
程序第一件事是给eax加载0值,获取厂商id,加载后必须要手机分散在三个输出寄存器的指令响应:
movl $output, %edi
movl %ebx,28(%edi)
movl %edx,32(%edi)
movl %ecx,36(%edi)
第一条指令创建一个指针,处理内存中声明output变量使用这个指针,output的标签位置会被加载到EDI寄存器中。接下来按照EDI指针,包含厂商ID字符串片段的三个寄存器的内容会被放到数据内存的正确位置。括号外表示output数据位置
其实就是
"the processor vendor ID is 'xxxxxxxxxxxx'\n"
xx开始的位置在28,xx有12个字节,然后他把eax、ebx、ecx的12个字节放到xxx里面,然后输出
放好字节后,就可以输出显示信息了
movl $4,%eax
movl $1,%ebx
movl $output,%ecx
movl $42,%edx
int $0x80
这段程序其实就是把output写到标准输出里
这个程序使用一个linux系统调用。linux内核提供很多容易的从汇编应用程序访问的内置函数,为了访问这些内核函数,必须要使用指令码,它生成具有0x80的软件中断。执行具体函数由EAX值来决定。如果没有这个内核函数,就必须 发送到正确显示器io地。
write 函数调用参数
eax包含系统调用的值
ebx写入文件描述符
ecx字符串开头edx包含的长度
3、调试程序
zhanglei@ubuntu:~/ass$ as -gstabs -o cpuid.o cpuid.s
zhanglei@ubuntu:~/ass$ ld -o cpuid cpuid.o
zhanglei@ubuntu:~/ass$ gdb cpuid
我们发现构建程序的时候多了一个-gstabs指令
打断点
到了这里就ok了
4、在汇编中使用c函数
使用printf
# cpuid2.s view the CPUID vendor id string using C library calls
.code32
.section .data
output:
.asciz "the processor vendor 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
printf 使用输入多个参数,这些参数取决于要显示的变量,第一个变量是要输出的字符串,带有显示变量的适当代码
output:
.asciz "the processor vendor id is '%s'\n"
注意是asciz而不是ascii,因为printf要以空字符结尾的字符串作为输出字符串。
因为不需要定义缓冲区的值,所以使用.lcomm命令为他声明12个字节的缓冲区
.section .bss
.lcomm buffer,12
字符串在push后使用call调用printf
pushl $buffer
pushl $output
call printf
addl $8,%esp
连接c库函数
zhanglei@ubuntu:~/ass$ ld cpuid2 cpuid2.o
ld: cannot find cpuid2: No such file or directory
zhanglei@ubuntu:~/ass$ ld -o cpuid2 cpuid2.o
ld: cpuid2.o: in function `_start':
(.text+0x1f): undefined reference to `printf'
ld: (.text+0x29): undefined reference to `exit'
zhanglei@ubuntu:~/ass$
会发现c库连接不正确
ld -o cpuid2 -lc cpuid2.o
接入动态c库后连接通过,但是仍然执行不正确
zhanglei@ubuntu:~/ass$ ./cpuid2
bash: ./cpuid2: No such file or directory
这是因为解析c函数,但是函数本身并没有包含在可执行 程序中。连接器在运行的时候需要找到必须的库文件,显然这个实例并不是这样。
这里比较老了,ubuntu已经没找到这个库,但是原理知道了不做实验了
5.总结
数据段包含特定内存位置中引用的数据。bss段包含未初始化的数据元素,比如工作缓冲区,例子中的buffer缓冲区
模板定以后注意起始点
为了使用c函数,必须要和宿主机的c库连接,为了更好做到这一点,我们学习了新命令dynamic-linker。