汇编程序 在 linux下编译,链接,运行

简介: 汇编程序 在 linux下编译,链接,运行

当前使用的操作系统:ubuntu11.10

汇编程序由定义好的段构成,每个段都有不同的目的,三个最常用的段:

1)data 段

        汇编程序 data(数据)段是可选的。

        数据段声明带有初始值的数据元素,这些数据元素用作汇编程序的变量。               

2)bss 段

        汇编程序 bss段 是可选的。

        bss段声明使用 零(或 NULL)值初始化的数据元素。这些元素最常用作汇编程序中的缓冲区

3)text 段

        汇编程序必须有 text(文本)段。

        这个段是 在可执行程序内声明指令码 的地方。


定义段:

GNU汇编器使用 .section 命令语句声明段。

.section 语句只使用一个参数------它声明的段的类型






定义起始点:

当汇编语言程序被转换为可执行文件时,连接器必须知道指令码中的起始点是什么。 



GNU 汇编器声明一个默认标签,或者说标识符,_start ,它应该用作应用程序的入口点。

_start 标签用于表明程序应该从这条指令开始运行。如果连接器找不到这个标签,就会生成如下错误消息:






汇编程序的基本模板如下:

      1
    

      2
    

      3
    

      4
    

      5
    

      6
    

      7
    

      8
    

      9
    

      10
    

      11
    


      .section    .data
     

              
      < initialized data here >
     

      .section    .bss
     

              
      < initialized data here>
     

        
     

      .section        .text
     

      .globl      _start
     

      _start:
     

              
      <instruction code=
      "" 
       goes=
      "" 
       here=
      ""
      >
     

        
     

      </instruction>
     





@a1@

      1
    

      2
    

      3
    

      4
    

      5
    

      6
    

      7
    

      8
    

      9
    

      10
    

      11
    

      12
    

      13
    

      14
    

      15
    

      16
    

      17
    

      18
    

      19
    

      20
    

      21
    

      22
    

      23
    


      .section    .data
     

      output:
     

          
      .ascii 
      "The processor Vendor ID is 'XXXXXXXXXXXXX'\n"
     

        
     

      .section    .text
     

      .globl  _start
     

      _start:
     

      #.globl main
     

      #main:
     

          
      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
     




sdf

CPUID 指令是一条汇编指令,它是请求处理器的特定信息并且把信息返回到特定寄存器的低级指令。

CPUID 指令使用单一寄存器值作为输入。EAX 寄存器用于决定 CPUID 指令生成什么信息。根据 EAX 寄存器的值, CPUID 指令在 EBX,ECX 和 EDX 寄存器中生成关于处理器的不同信息。






本例子中,使用零选项从处理器获得简单的厂商ID字符串。

当 零(0)值被放入到 EAX 寄存器并且执行 CPUID 指令时,处理器把厂商ID字符串返回到 EBX,EDX 和 ECX 寄存器中,如下:

    1)EBX 包含字符串的最低 4 个字节

    2)EDX 包含字符串的中间 4 个字节

    3)ECX 包含字符串的最高 4 个字节

字符串按照 小端法(little-endian)格式放入寄存器中:



    @b1@ 在数据段中声明了一个字符串:









      1
    

      2
    


      output:
     

          
      .ascii 
      "The processor Vendor ID is 'XXXXXXXXXXXXX'\n"
     








        .ascii 声明使用 ASCII 字符声明一个文本字符串。

        这个字符串元素被预定义并且放在内存中,其起始内存位置由 output标识符 指示。

        后面的 x 在保留给数据变量的内存区域中作为占位符,从处理器获得厂商ID字符串时,会把这些字符串放在位于这些内存位置的数据中。




    @b2@ 声明程序的指令码和一般的起始位置:










      1
    

      2
    

      3
    


      .section    .text
     

      .globl  _start
     

      _start:
     








    @b3@ 程序做的第一件事就是使 EAX 寄存器 加载 零 值,然后运行 CPUID 指令:








      1
    

      2
    


      <strong> movl $0, %eax
     

          
      cpuid</strong>
     








    @b4@ EAX中的零值定义 CPUID 输出选项(在本例子 中是厂商ID字符串)。

       CPUID指令运行之后,必须收集分散在 3个 输出寄存器中的指令响应:









      1
    

      2
    

      3
    

      4
    


      movl    $output,    %edi
     

      movl    %ebx,       28(%edi)
     

      movl    %edx,       32(%edi)
     

      movl    %ecx,       36(%edi)
     








        首先创建一个指针,处理内存中声明的 output 变量时会使用这个指针。

        output 标识符的内存位置被加载到 EDI 寄存器中。

        接下来,按照 EDI 指针,包含厂商ID字符串片段的 3 个寄存器的内容被放到数据内存中的正确位置。

        括号外的数值表示相对于 output标识符的放置数据的位置。这个数字和 EDI 寄存器中的地址相加,确定寄存器中的值被写入的地址。这个过程使用实际的厂商ID字符串片段替换用作 x 占位符(注意:厂商ID字符串按照奇怪的顺序 EBX,EDX 和 ECX 分散在寄存器中) 。




    @b5@ 在内存中放置好所有厂商ID字符串片段之后,就可以显示信息了:







      1
    

      2
    

      3
    

      4
    

      5
    


      movl    $4,         %eax
     

      movl    $1,         %ebx
     

      movl    $output,    %ecx
     

      movl    $42,        %edx
     

      int     
       $0x80
     


            系统调用表:http://blog.chinaunix.net/uid-28458801-id-3477399.html




        使用一个 linux 系统调用(int $0x80)从 linux 内核访问控制台显示。linux 内核提供了很多可以很容易地从汇编应用程序访问的预置函数。为访问内核函数,必须使用 int 指令码,它生成具有 0x80 值的软中断。

        执行的具体函数有EAX寄存器的值来确定。如果没有这个内核函数,就必须自己把每个输出字符发送到正确的显示器 I/O地址上。

   

        4 对应的是 sys_write

        linux 的 write 系统调用用于把字节写入到文件中。

            1)EAX包含系统调用的值

            2)EBX包含要写入的文件描述符

            3)ECX包含字符串的起始地址

            4)EDX包含字符串的长度




        标准输出(STDOUT)表示当前会话的显示终端,它的文件描述符为 1,。写入到这个文件描述符将在控制台屏幕上显示信息。

    @b6@ 显示完信息后,就应该退出程序:






      1
    

      2
    

      3
    


      movl    $1,         %eax
     

      movl    $0,         %ebx
     

      int     
       $0x80
     

sdf

        通过系统调用1(sys_exit()),程序被正确的终止,并且返回到命令提示符,零值表示程序成功执行。

        EBX 寄存器包含程序返回给shell的退出代码值。可以使用 EBX寄存器的内容,按照汇编程序内的情况在shell脚本程序中生成不同的结果。

    @b DONE@




生成可执行文件:






运行可执行文件:







用 gcc 编译:

    1,修改代码如下:









      1
    

      2
    

      3
    

      4
    

      5
    


      .section    .text
     

      #.globl _start
     

      #_start:
     

      .globl  main
     

      main:
     









调试程序:(为了调试汇编程序,必须使用  -gstabs 参数编译源文件)





@a2@ 在汇编程序中使用 C库函数

      1
    

      2
    

      3
    

      4
    

      5
    

      6
    

      7
    

      8
    

      9
    

      10
    

      11
    

      12
    

      13
    

      14
    

      15
    

      16
    

      17
    

      18
    

      19
    

      20
    

      21
    

      22
    


      .section    .data
     

      output:
     

          
      .asciz  
      "The process vandor 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
     





    @b1@ .asciz 命令


        printf 函数使用多个输入参数,这些参数取决于要显示的变量。第一个参数是输出字符串,带有用于显示变量:








      1
    

      2
    


      output:
     

          
      .asciz  
      "The process vandor id is '%s'\n"
     




sfsdf

        注意:这里使用的是 .asciz 命令,而不是 .ascii 。printf 函数要求以空字符串结尾的作为输出字符串。 .asciz 命令在定义的字符串末尾添加空字符。




    @b2@ .lcomm 命令


        printf函数要使用的下一个参数是将要包含 厂商ID字符串的缓冲区。因为不需要定义这个缓冲区的初始值,所以在 bss段中使用 .lcomm 命令把它声明为 12 个字节的缓冲区区域:









      1
    

      2
    


      .section    .bss
     

          
      .lcomm  buffer, 12
     








    @b3@ PUSHL指令









      1
    

      2
    

      3
    


      pushl   $buffer
     

      pushl   $output
     

      call    
      printf
     





            为了把参数传递给 C 函数 printf,必须把参数压入堆栈中。这就要使用 PUSHL 指令完成。 

            参数放入堆栈的顺序和 printf 函数获取它们的顺序是相反的,所以缓冲区值被首先放入,然后是输出字符串值。

            完成这些操作后, 使用  CALL 指令调用 printf 函数。   





    @b4@ 程序退出










      1
    

      2
    

      3
    


      addl    $8,     %esp
     

      pushl   $0
     

      call    
      exit
     







         ADDL 指令用于清空 为 printf 函数放入堆栈中的参数。 



        再通过 PUSHL 指令把 0 压入堆栈中,

        最后调用系统函数 exit,参数为 0,也就是exit(0);表示程序正常退出。  

    @b DONE@




连接C库函数           

    1)在汇编程序中使用 C 库函数时,必须把 C库文件连接到程序目标代码中,如果C库函数不可用,连接 器会发生如下错误:






    在 linux 系统上,标准的 C 动态库位于 libc.so.x 文件中,其中 x 表示库的版本。

    这个库文件包含了标准 C 函数,包括 printf 和 exit。

    在使用 gcc 时,这个文件会被自动连接到 C 程序中。但是在汇编程序中,必须手动把它连接到程序目标代码中以便 C 函数能够操作。为了连接 libc.so 文件,必须使用 GNU 连接器的 -l 参数:



    这个时候,连接了标准C函数库文件的程序目标代码没有问题。可是当执行这个可执行文件时,却发生了错误。

    原因:连接器能够解析 C 函数,但是函数本身没有包含到最终的可执行程序中,我们使用的是动态链接库,连接器假设运行时能够找到必须的库文件。显然,本例子中并不是这样。




    为解决这个问题,还必须指定在运行时加载动态库的程序:


@a DONE@

相关文章
|
3天前
|
Linux 数据处理
探索Linux下的readlink命令:解析符号链接的利器
`readlink`命令在Linux中用于揭示符号链接的指向,显示它们所链接的实际文件或目录的路径。它可以显示简洁的绝对路径(-f),处理循环链接(-e),或不加换行符输出(-n)。例如,查看`link.txt`指向:`readlink link.txt`;获取绝对路径:`readlink -f link.txt`。使用时要注意链接是否存在、权限问题和可能的循环链接。
|
5天前
|
缓存 Linux 编译器
技术笔记:Linux程序包管理
技术笔记:Linux程序包管理
|
5天前
|
监控 网络协议 关系型数据库
如何在Linux中查看正在运行的进程以及过滤特定端口和进程名称
如何在Linux中查看正在运行的进程以及过滤特定端口和进程名称
9 0
|
5天前
|
Java 编译器 Linux
程序技术好文:详解Linux安装GCC方法
程序技术好文:详解Linux安装GCC方法
11 0
|
9天前
|
监控 数据挖掘 大数据
Linux中的nohup命令:让你的任务在后台持久运行
**Linux的`nohup`命令让任务在后台持续运行,即使终端关闭。它创建不受终端影响的新进程,常用于长时间任务,如数据处理。`nohup`将输出重定向至`nohup.out`,可使用`-p`选项避免此行为。示例:`nohup ./script.sh &gt; output.log 2&gt;&1 &`。记得检查输出、重定向、记录命令、监控任务并使用日志管理工具。**
|
10天前
|
Linux vr&ar C语言
Linux怎样更新Centos下Gcc版本支持C17?Centos7快速安装gcc8.3.1 可支持C++17(附gcc相关链接整理)
Linux怎样更新Centos下Gcc版本支持C17?Centos7快速安装gcc8.3.1 可支持C++17(附gcc相关链接整理)
34 2
|
10天前
|
Linux C语言 C++
Linux 下centos 查看 -std这个编译时命令 是否支持 C17
Linux 下centos 查看 -std这个编译时命令 是否支持 C17
17 2
|
10天前
|
Linux
Linux yum 运行时提示编码问题错误
Linux yum 运行时提示编码问题错误
9 3
|
10天前
|
缓存 监控 安全
Linux top命令详解:持续监听进程运行状态
Linux top命令详解:持续监听进程运行状态
25 3
|
11天前
|
Shell Linux 网络安全
Linux怎样在使用ssh 链接时就指定gcc 的版本
Linux怎样在使用ssh 链接时就指定gcc 的版本
19 7