linux平台学x86汇编(四):从“hello world!”开始

【版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途】

汇编语言程序由定义好的段构成,每个段有各自的目的。三个最常用的的段如下:数据段、bss段、文本段。文本段是可执行程序内声明指令码的地方,所有汇编程序都必须有文本段,数据段和bss段是可选的,但是在程序中经常使用。数据段声明带有初始值的变量,bss段声明使用0值初始化的数据元素,这些元素常用作汇编程序的缓冲区。下图为汇编语言程序的布局。

GNU汇编器使用.section命令语句声明段。.section语句只有一个参数——段类型。上图的布局是汇编程序安排段的一般方式。bss段总是在文本段之前,数据段可以在文本段之后,但将其放在前面更容易阅读和理解。

如其它高级语言一样,汇编语言程序在链接为可执行程序时,链接器必须要知道程序中的起点是什么,就像c语言中的main函数一样。GNU汇编器使用一个默认标签_start作为应用程序的入口点,如果链接器找不到这个标签就会生成错误消息。如果编写被外部汇编语言或C语言程序使用的一组工具,需要使用.globl命令声明每个函数段标签,.globl命令是声明外部程序可以访问的程序标签。所以,一般编写汇编语言的基础模板是这样的:

.section.data
    <此处为初始化变量>
.section.bss
    <此处为未初始化的变量>
.section.text
.globl _start
_start:
    <此处为指令码>

有了模板之后就可以开始创建汇编语言程序,我们也像学习高级语言一样,从最简单的程序开始。

编写汇编语言主要工作在编写.text部分,该部分主要编写要实现应用程序的指令码。汇编语言允许程序员使用助记符表示指令码,助记符使程序员可以使用英语样式的词表示各个指令码,汇编器可以很容易地把汇编语言助记符转换为原始指令码。这样使得汇编程序员不必了解指令码每个字节表示什么,子需要使用更加容易记忆的助记符(如push、mov、sub、call)来表示指令码。比如下面这个指令码例子:

55
89 E5
83 EC 08
C7 45 FC 01 00 00 00
83 EC 0C
6A 00
E8 D1 FE FF FF

可以写为如下的汇编代码:

push %ebp
mov  %esp, %ebp
sub  $0x8, %esp
movl $0x1, -4(%ebp)
sub  $0xc, %esp
push $0x0
call 8048348
  • 数据段

和高级语言一样,编写汇编语言程序都需要管理某种类型的变量,在汇编语言中数据段和bss段都提供了定义变量的方法。数据段是最常见的定义变量的位置。

在数据段中定义变量需要两个语句:一个符号、一个命令。

符号类似于C语言程序中变量的名称,它只是汇编器试图访问内存位置时用作引用指针的一个位置。

命令实现为符号引用的数据元素保留多少字节,类似于高级语言指定数据类型。汇编语言使用如下的命令:.ascii、.asciz、.byte、.double、.float、.int、.long、.octa、.quad、.short、.single。例如定义变量如下:

.section .data
msg:
        .ascii “This is a test message”

数据段主要用于定义变量数据,不过也可以使用命令.equ定义静态数据符号,类似于高级语言的定义常量。例如:

.equ var 3</span>

引用静态数据时,需要在变量名称前加$符号,比如把var的值传送到EAX寄存器:

movl $var, %eax
  • bss段

在bss段中定义数据元素和在数据段中定义有些不同,不需要指定特定数据类型。

GNU汇编器使用两个命令声明缓冲区,.comm命令声明未初始化的数据的通用内存区域;.lcomm命令声明未初始化的数据本地通用内存区域,该区域不允许从本地汇编代码之外进行访问。其使用格式为:

.comm symbol, length

symbol是赋给内存区域的符号,length是内存区域中包含的字节数量。

在bss段中声明数据的一个好处是数据不包含在可执行程序中,在数据段中定义数据必须包含在可执行程序中。

下面来看看汇编语言的hello world 程序:

#hello.s sample program to print hello world information
.section .data    #数据段声明
msg:
    .ascii "hello world!\n"    #要输出的字符串
    len=.-msg                        #字符串长度

.section .text    #代码段声明
# .global main
# main:
.global _start     #指定入口函数

_start:                                 #函数在屏幕上输出hello world!
movl $len, %edx               #第三个参数: 字符串长度
movl $msg, %ecx             #第二个参数: hello world!字符串
movl $1, %ebx                  #第一个参数: 输出文件描述符
movl $4, %eax                  #系统调用号sys_write
int $0x80                            #调用内核功能

#下面为退出程序代码
movl $0, %ebx                #第一个参数: 退出返回码
movl $1, %eax                #系统调用sys_exit
int $0x80                        #调用内核功能

编译执行结果如下:

$ as -o hello.o hello.s
$ ld -o hello hello.o
$ ./hello hello world!$

Linux 下的系统调用是通过中断(int 0x80)来实现的。在执行 int 0X80 指令时,寄存器 eax 中存放的是系统调用号,而传给系统调用的参数则必须按顺序放到寄存器 ebx,ecx,edx,esi,edi 中,当系统调用完成之后,返回值可以在寄存器 eax 中获得。系统调用号4对应的函数调用是

sys_write,在应用上其函数定义如下:

ssize_t write(int fd, const void *buf, size_t count);

参数 fd、buf 和 count 分别存在寄存器 ebx、ecx 和 edx 中,而系统调用号 SYS_write 则放在寄存器 eax 中,当 int 0x80 指令执行完毕后,返回值可以从寄存器 eax 中获得。

注意,如果使用gcc编译的话有一个问题,gcc查找main标签而不是_start标签,所以把程序中的_start改为main直接使用gcc编译链接就没有问题了。

时间: 2024-08-02 06:54:27

linux平台学x86汇编(四):从“hello world!”开始的相关文章

linux平台学x86汇编(十四):函数的使用

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 和高级语言一样,汇编语言在多个项目之间可能会编写相同的过程和处理,如果使用函数的话就可以不必每次需要时都重复编写实用程序代码,从而在需要它的时候调用它. 函数包含完成特定功能所需的代码,数据从主程序传递给函数,然后结果返回给主程序.调用函数时,程序执行路径被改变,切换到函数代码的第一条指令.处理器从这个位置开始执行指令,直到函数表明它可以把控制返回到主程序中的原始位置. 在汇

linux平台学x86汇编(六):数据的传送

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 前面讲了定义数据元素,既然定义了数据元素,那么就需要知道如何处理这些数据元素.数据元素位于内存中,并且处理器很多指令要使用寄存器,所以处理数据元素的第一个步骤就是在内存和寄存器之间传送它们.数据传送指令为mov,其为汇编语言中最常用的指令之一. mov指令的基本格式如下: movx source, dest 其中source和dest的值可以是内存地址.存储在内存中的数值.指

linux平台学x86汇编(三):相关开发工具

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 类似于其它高级语言,编写汇编语言,必须有一个开发环境,那么也就需要适当的工具了.搭建汇编语言至少应该有下面这些工具:汇编器.链接器.调试器.下下面看看在汇编语言开发环境中如何使用它们. 汇编器 汇编器用于把汇编语言源代码转换为处理器指令码.选择的汇编器必须能够生成所在系统的处理器系列指令码.汇编语言源代码程序有3个部分:操作码助记符.数据段.命令.但是每种汇编器对于每个部分使

linux平台学x86汇编(十九):C语言中调用汇编函数

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 除了内联汇编以外,还有一种途径可以把汇编代码整合到C/C++语言中,C/C++语言可以直接调用汇编函数,把输入值传递给函数,然后从函数获得输出值. 如果希望汇编语言函数和C/C++程序一起工作,就必须显示地遵守C样式的函数格式,也就是说所有输入变量都必须从堆栈读取,并且大多数输入值都返回到EAX嫁寄存器中.在汇编函数代码中,C样式函数对于可以修改哪些寄存器和函数必须保留哪些寄

linux平台学x86汇编(五):使用gdb调试汇编程序

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 正如C语言一样,编写所有语言程序一样会出现一些一些错误,发生错误时,我们可以使用调试器一步一步运行程序以监视数据是如何被处理的.本节使用GNU调试器检查上一节hello程序,监视处理过程中寄存器和内存的值的变化.要调试汇编语言程序,在编译时,需要使用-gstabs参数重新汇编源代码,使用了该参数编译出来的可执行文件要比之前稍大一些,因为添加了附加信息.上一节程序不使用-gst

linux平台学x86汇编(二十):汇编库的使用

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 汇编语言和C一样,可以通过使用库来简化阻止大量函数的目标文件的问题.GNU C编译器可以不在命令行中独立地包含每个独立地函数目标文件,它允许吧所有目标文件组合在单一存档文件中.在编译C程序时,要做的工作就是包含单一的目标库文件,在编译时,编译器可以从库文件中挑出所需的正确目标文件.在库文件中,经常按照应用程序类型或者函数类型把函数分组在一起,单一应用程序项目可以使用多个库文件

linux平台学x86汇编(二):处理器指令码及IA-32平台了解

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 在计算机操作的最底层,所有计算机处理器都按照制造厂商在处理器内部定义的二进制代码来操作数据,这些代码定义了处理器应该利用程序员提供的数据完成相应的功能,这些预置的代码被称为指令码.不同类型的处理器的指令码是不一样的,但处理指令码的方式是类似的. 当计算机处理器芯片运行时,他会读取存储在内存中的指令码,每个指令码包含不同长度字节的信息,这些信息指示处理器完成特定任务.每条指令码

linux平台学x86汇编(八):条件跳转

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 在此之前我们使用的汇编代码示例都是从第一条指令开始,直到最后最后一条指令程序退出.但实际上和高级语言类似,汇编代码也提供指令来改变程序处理数据方式. 正常情况下,程序要执行要执行的下一条指令是在指令指针寄存器中,指令指针确定程序中哪条指令是应该执行的下一条指令. 当指令指针在程序指令中移动时,EIP寄存器会递增.指令长度可能是多个字节,所以指向下一条指令不仅仅是每次是指令指针

linux平台学x86汇编(十八):内联汇编

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 使用汇编语言笔编程最常见的方式是在高级语言(C和C++)程序内编写汇编函数,这种吧汇编语言直接写到C和C++语言程序内的技术称为内联汇编. GNU的C编译器使用asm关键字指出使用汇编语言编写的源代码段落.asm段的基本格式如下: asm("as code"): 括号中的汇编指令必须在括号,指令超过一条的话必须使用新的行分隔汇编语言代码每一行,因为编译器逐字地取得a