汇编语言范例

本文介绍了汇编语言程序的组成,包括数据段、bss段和文本段的定义,强调了_start标签在确定程序起始点的重要性。通过示例展示了如何使用cpuid指令获取处理器信息并输出。同时,讲解了如何在汇编中调用C函数,如printf,以及遇到的问题和解决方法。最后,讨论了调试程序和动态链接c库的必要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章参考书籍:

汇编语言程序设计

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值