汇编程序 在 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@

相关文章
|
16天前
|
安全 Linux Shell
Linux上执行内存中的脚本和程序
【9月更文挑战第3天】在 Linux 系统中,可以通过多种方式执行内存中的脚本和程序:一是使用 `eval` 命令直接执行内存中的脚本内容;二是利用管道将脚本内容传递给 `bash` 解释器执行;三是将编译好的程序复制到 `/dev/shm` 并执行。这些方法虽便捷,但也需谨慎操作以避免安全风险。
|
23天前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
99 2
|
23天前
|
Linux Python
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
40 2
|
18天前
|
Linux
用clang编译Linux内核
用clang编译Linux内核
|
1天前
|
存储 传感器 Linux
STM32微控制器为何不适合运行Linux系统的分析
总的来说,虽然技术上可能存在某些特殊情况下将Linux移植到高端STM32微控制器上的可能性,但从资源、性能、成本和应用场景等多个方面考虑,STM32微控制器不适合运行Linux系统。对于需要运行Linux的应用,更适合选择ARM Cortex-A系列处理器的开发平台。
14 0
|
24天前
|
Linux C语言
深度探索Linux操作系统 —— 编译过程分析
深度探索Linux操作系统 —— 编译过程分析
16 2
|
20天前
|
Linux
用QEMU模拟运行uboot从SD卡启动Linux
用QEMU模拟运行uboot从SD卡启动Linux
|
23天前
|
Linux Windows Python
最新 Windows\Linux 后台运行程序注解
本文介绍了在Windows和Linux系统后台运行程序的方法,包括Linux系统中使用nohup命令和ps命令查看进程,以及Windows系统中通过编写bat文件和使用PowerShell启动隐藏窗口的程序,确保即使退出命令行界面程序也继续在后台运行。
|
11天前
|
Linux Shell
Linux 中 Tail 命令的 9 个实用示例
Linux 中 Tail 命令的 9 个实用示例
40 6
Linux 中 Tail 命令的 9 个实用示例