Linux x86_64 汇编语言的编写

这篇具有很好参考价值的文章主要介绍了Linux x86_64 汇编语言的编写。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、代码示例1

1.1 代码说明

.section .data
message:
	.string "Hello, World!\n"
len = . - message

.section .text
.globl _start
_start:
	# 调用 write() 函数输出 "Hello, World!"
	mov $1, %rax            # 系统调用号为 1 表示 write()
	mov $1, %rdi            # 文件描述符为 1 表示标准输出
	lea message(%rip), %rsi # 输出的字符串地址
	mov $len, %rdx          # 输出的字符串长度
	syscall                 # 调用系统调用

	# 调用 exit() 函数退出程序
	mov $60, %rax           # 系统调用号为 60 表示 exit()
	xor %rdi, %rdi          # 返回值为 0
	syscall                 # 调用系统调用

这段汇编代码是在标准输出上输出 “Hello, World!”,然后退出程序。

as -o hello.o hello.s
ld -o hello hello.o
# ./hello
Hello, World!

首先,在 .data 段中定义了一个名为 message 的字符串,内容为 “Hello, World!\n”。.data 段用于定义程序中的静态数据,这些数据在程序运行期间不会被修改。

接下来,在 .text 段中定义了一个全局标号 _start,这是程序的入口点。.text 段用于定义程序的代码。

在 _start 标号下,使用汇编指令依次完成以下操作:
(1)将系统调用号 1(表示 write() 函数)存储到 %rax 寄存器中。
(2)将文件描述符 1(表示标准输出)存储到 %rdi 寄存器中。
(3)使用 lea 指令将字符串 message 的地址存储到 %rsi 寄存器中。lea 指令可以计算一个地址,并将计算结果存储到目标寄存器中。
(4)将字符串的长度 len 存储到 %rdx 寄存器中。
(5)使用 syscall 指令调用系统调用,将输出字符串 “Hello, World!\n” 到标准输出。
(6)接下来,将系统调用号 60(表示 exit() 函数)存储到 %rax 寄存器中,将返回值 0 存储到 %rdi 寄存器中,然后使用 syscall 指令调用系统调用,退出程序。

NAME
       write - write to a file descriptor

SYNOPSIS
       #include <unistd.h>

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

DESCRIPTION
       write() writes up to count bytes from the buffer pointed buf to the file referred to by the file descriptor fd.
fd(文件描述符) 描述
文件描述符0:标准输入(stdin) 文件描述符0通常用于标准输入,也就是从键盘或者其他标准输入设备读取数据。当进程从标准输入读取数据时,实际上是从文件描述符0所表示的输入流中读取数据。
文件描述符1:标准输出(stdout) 文件描述符1通常用于标准输出,也就是向屏幕或者其他标准输出设备输出数据。当进程向标准输出写入数据时,实际上是向文件描述符1所表示的输出流中写入数据。
文件描述符2:标准错误输出(stderr) 文件描述符2通常用于标准错误输出,也就是向屏幕或者其他标准错误输出设备输出错误信息。当进程向标准错误输出写入数据时,实际上是向文件描述符2所表示的输出流中写入数据。
寄存器 说明
rax 系统调用号/返回值 系统调用号 = 1 表示 write() 函数
rdi 第一个参数 fd =1 表示标准输出
rsi 第二个参数 buf = message 字符串的地址
rdx 第三个参数 count = strlen(message) = len

1.2 系统调用号

在 Linux x86_64 系统中,系统调用使用 syscall 汇编指令来执行。每个系统调用都有一个唯一的系统调用号(system call number),用于标识要执行的系统调用。这个系统调用号存储在 %rax 寄存器中,syscall 指令会根据 %rax 寄存器中的系统调用号来执行相应的系统调用。

# cat /usr/include/asm/unistd_64.h | grep write
#define __NR_write 1
# cat /usr/include/asm/unistd_64.h | grep exit
#define __NR_exit 60

1.3 lea 指令

lea 指令(Load Effective Address)可以计算出一个有效地址,并将其存储到指定的寄存器中。它的基本语法如下:

lea offset(base, index, scale), destination

(1)offset 是一个可选的常数偏移量,表示需要加到基址上的偏移量。如果没有常数偏移量,则可以省略这个部分。
(2)base 是一个可选的基址寄存器,表示需要加上偏移量的地址,可以是 64 位寄存器或内存地址。
(3)index 是一个可选的变址寄存器,表示需要加上偏移量的地址,可以是 64 位寄存器。
(4)scale 是一个可选的比例因子,表示变址寄存器的值需要乘上的比例因子,可以是 1、2、4 或 8。
(5)destination 是需要存储结果的寄存器,可以是 64 位寄存器。

lea 指令将根据给定的基址寄存器、变址寄存器、比例因子和常数偏移量计算出一个有效地址,并将其存储到目标寄存器中。例如,lea (%rax, %rbx, 4), %rdx 指令将计算出 %rax + 4 * %rbx 的值,并将结果存储到 %rdx 中。

在这个例子中,lea message(%rip), %rsi 指令使用了 %rip 寄存器作为基址寄存器,表示当前指令的地址。message 符号表示一个字符串常量,它的地址在程序的数据段中被定义。由于 %rip 寄存器存储的是当前指令的地址,因此 message(%rip) 表示当前指令的地址与 message 符号的地址之差。这个差值是一个 32 位的相对偏移量,它在编译时可以计算出来并存储在指令中。因此,这个指令的作用是将字符串 message 的地址与当前指令的地址相加,计算出字符串的有效地址,并将这个地址存储到 %rsi 寄存器中。

lea message(%rip), %rsi # 输出的字符串地址

这条指令的机器码是:48 8d 35 <address>,其中 <address> 是一个 32 位的相对地址,用于计算 message 字符串相对于指令地址的偏移量。具体来说,48 是一个前缀字节,用于指示这是一个 64 位指令;8d 是 lea 操作码;35 是 Mod R/M 字节,用于指示源操作数的地址模式和目标操作数的寄存器编号;<address> 是一个 32 位的相对地址,用于计算 message 字符串的地址。

<address> 的值是一个相对于下一条指令地址的偏移量,而不是一个绝对地址。

]# objdump -d hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <_start>:
   0:   48 c7 c0 01 00 00 00    mov    $0x1,%rax
   7:   48 c7 c7 01 00 00 00    mov    $0x1,%rdi
   e:   48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # 15 <_start+0x15>
  15:   48 c7 c2 0e 00 00 00    mov    $0xe,%rdx
  1c:   0f 05                   syscall
  1e:   48 c7 c0 3c 00 00 00    mov    $0x3c,%rax
  25:   48 31 ff                xor    %rdi,%rdi
  28:   0f 05                   syscall
   e:   48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # 15 <_start+0x15>

提取机器码:

objdump -d ./hello|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
# objdump -d ./hello1|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x48\xc7\xc0\x01\x00\x00\x00\x48\xc7\xc7\x01\x00\x00\x00\x48\x8d\x35\x15\x00\x20\x00\x48\xc7\xc2\x0f\x00\x00\x00\x0f\x05\x48\xc7\xc0\x3c\x00\x00\x00\x48\x31\xff\x0f\x05"

二、代码示例2

.global _start

.section .data
input_buffer:
  .skip 1024

prompt:
  .ascii "Enter some text: "

prompt_len = . - prompt

.section .text
_start:
  # 输出提示信息
  movq $1, %rax            # 系统调用号为 1 表示 write()
  movq $1, %rdi            # 文件描述符为 1 表示标准输出
  leaq prompt(%rip), %rsi  # 输出的字符串地址
  movq $prompt_len, %rdx   # 输出的字符串长度
  syscall                  # 调用系统调用

  # 读取用户输入
  movq $0, %rax            # 系统调用号为 0 表示 read()
  movq $0, %rdi            # 文件描述符为 0 表示标准输入
  leaq input_buffer(%rip), %rsi  # 存储输入的字符串地址
  movq $1024, %rdx         # 读取的最大字节数
  syscall                  # 调用系统调用

  # 输出用户输入
  movq $1, %rax            # 系统调用号为 1 表示 write()
  movq $1, %rdi            # 文件描述符为 1 表示标准输出
  movq %rsi, %rsi          # 输入的字符串地址
  syscall                  # 调用系统调用

  # 退出程序
  movq $60, %rax           # 系统调用号为 60 表示 exit()
  xorq %rdi, %rdi          # 返回值为 0
  syscall                  # 调用系统调用
# as -o read.o read.s
# ld -o read read.o
# ./read
Enter some text: ni hao
ni hao
# cat /usr/include/asm/unistd_64.h | grep read
#define __NR_read 0
NAME
       read - read from a file descriptor

SYNOPSIS
       #include <unistd.h>

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

DESCRIPTION
       read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf

三、代码示例3

3.1 代码演示

.section .text
.globl _start
_start:
	# 调用 execve() 函数执行 /bin/sh 命令
	xor %rax, %rax
	movb $59, %al           # 系统调用号为 59 表示 execve()
	xor %rdi, %rdi          # 参数 1,文件路径
	movq $0x68732f6e69622f, %rdi  # /bin/sh 的 ASCII 码
	pushq %rdi              # 入栈
	movq %rsp, %rdi         # 参数 1,命令行参数
	xor %rsi, %rsi          # 参数 2,环境变量
	xor %rdx, %rdx          # 参数 3,未使用
	syscall                 # 调用系统调用

	# 调用 exit() 函数退出程序
	movq $60, %rax          # 系统调用号为 60 表示 exit()
	xor %rdi, %rdi          # 返回值为 0
	syscall                 # 调用系统调用

通过调用 execve() 函数执行 /bin/sh 命令,即打开一个新的 shell 终端。

# ps
  PID TTY          TIME CMD
17510 pts/0    00:00:00 bash
21407 pts/0    00:00:00 ps
# ./execve
# ps
  PID TTY          TIME CMD
17510 pts/0    00:00:00 bash
21426 pts/0    00:00:00 sh
21465 pts/0    00:00:00 ps
# exit
exit
# ps
  PID TTY          TIME CMD
17510 pts/0    00:00:00 bash
21550 pts/0    00:00:00 ps

execve:

# cat /usr/include/asm/unistd_64.h | grep execve
#define __NR_execve 59
NAME
       execve - execute program

SYNOPSIS
       #include <unistd.h>

       int execve(const char *filename, char *const argv[],
                  char *const envp[]);

3.2 代码说明

(1)

xor %rax, %rax

这行指令将 rax 寄存器的值设置为 0,使用异或操作实现清零操作。

(2)

movb $59, %al

这行指令将系统调用号 59(即 execve() 函数)存储在 al 寄存器中。由于 al 寄存器是 rax 寄存器的低 8 位,因此可以使用 movb 指令将一个字节的值存储在 al 寄存器中。

(3)

xor %rdi, %rdi

这行指令将 rdi 寄存器的值设置为 0,表示参数 1(文件路径)为空。

(4)

movq $0x68732f6e69622f, %rdi

这行指令将 /bin/sh 的 ASCII 码存储在 rdi 寄存器中。具体来说,0x68732f6e69622f 是 /bin/sh 字符串的 ASCII 码的 64 位表示方式,由于 x86_64 架构是小端字节序,因此需要将高位字节存储在低地址。

这里给出两种获取字符串 ASCII 码的方法:
第一种方法:
/bin/sh 的 ASCII 码:

# echo -n "/bin/sh" | xxd -g 1
0000000: 2f 62 69 6e 2f 73 68

由于 x86_64 架构是小端字节序,因此需要将高位字节存储在低地址。

即:0x68732f6e69622f

第二种方法:

# python
>>> string = "/bin/sh"
>>> string[::-1].encode('hex')
'68732f6e69622f'

(5)

pushq %rdi

这行指令将 rdi 寄存器的值入栈,即将 /bin/sh 字符串的地址入栈。这是为了将字符串的地址传递给 execve() 函数。

(6)

movq %rsp, %rdi

这行指令将栈顶指针存储在 rdi 寄存器中,表示参数 2(命令行参数)。由于 /bin/sh 字符串的地址已经入栈,因此栈顶指针即为字符串的地址。

(7)

xor %rsi, %rsi
xor %rdx, %rdx

这两行指令分别将参数 3(环境变量)和参数 4(未使用)清零,使得 execve() 函数不需要传递这些参数。由于这两个参数都可以为空,因此可以使用异或操作清零。

(8)

syscall

这行指令调用 execve() 系统调用,执行 /bin/sh 命令。

在 Linux x86_64 架构下:
执行系统调用时,系统调用号存储在 rax 寄存器中,参数 1~6 存储在 rdi、rsi、rdx、r10、r8 和 r9 寄存器中。
进行普通的函数调用时,rax 寄存器用于保存返回值,参数 1~6 存储在 rdi、rsi、rdx、rcx、r8 和 r9 寄存器中。
文章来源地址https://www.toymoban.com/news/detail-532725.html

到了这里,关于Linux x86_64 汇编语言的编写的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 单片机温度检测高温报警系统 汇编语言

    单片机温度检测高温报警系统 实验目的 1 ) 掌握 51 单片机定时器/计数器工作原理与应用; 2 ) 掌握 51 单片机中断系统工作原理与应用; 3 ) 掌握 Keil μ Vision 软件开发环境,汇编语言源程序的编制与调试; 4 )掌握ds18b20温度检测传感器使用方法 5 )掌握单片机拓展总线的

    2024年02月12日
    浏览(31)
  • 实验一8086计CPU系统寻址方式和汇编语言程序设计

    实验一8086计CPU系统寻址方式和汇编语言程序设 一、实验目的 (1)掌握8086CPU系统的逻辑地址和寻址方式。 (2)掌握8086CPU系统中机器数的表示方式。 (3)掌握指令的机器码表示方法。 (4)掌握堆栈的概念和操作过程。 (5)掌握集成开发环境下的程序设计和调试方法。 (6)掌握汇编语言实

    2023年04月22日
    浏览(34)
  • 从零学习开发一个RISC-V操作系统(四)丨RISC-V汇编语言编程

       本系列是博主参考B站课程学习开发一个RISC-V的操作系统的学习笔记,计划从RISC-V的底层汇编指令学起,结合C语言,在Ubuntu 20.04上开发一个简易的操作系统。一个目的是通过实践操作学习和了解什么是操作系统,第二个目的是为之后学习RISC-V的集成电路设计打下一定基础

    2024年01月25日
    浏览(31)
  • 汇编语言笔记(一)——汇编语言基础

    一、开发环境 我使用visual studio 2022 preview,其他版本的设置大同小异。 第一步: 打开visual studio,点击“创建新项目”: 第二步: visual studio并没有专门的汇编项目,所以需要挂羊头卖狗肉,选择C++空项目 第三步: 输入项目名称,点击创建 第四步: 鼠标右键单击项目名称—

    2024年02月05日
    浏览(30)
  • 汇编语言—常见汇编指令汇总

    mov    寄存器 ,数据                如:mov ax ,8 mov   寄存器,寄存器              如:mov ax,bx mov   寄存器,内存单元          如:mov ax,[0] mov   内存单元,寄存器          如:mov [0],ax mov   段寄存器,寄存器          如:mov ds,ax add   寄存器,数据

    2024年02月10日
    浏览(28)
  • 5.6 汇编语言:汇编高效数组寻址

    数组和指针都是用来处理内存地址的操作,二者在C语言中可以互换使用。数组是相同数据类型的一组集合,这些数据在内存中是连续存储的,在C语言中可以定义一维、二维、甚至多维数组。多维数组在内存中也是连续存储的,只是数据的组织方式不同。在汇编语言中,实现

    2024年02月11日
    浏览(32)
  • 5.8 汇编语言:汇编高效除法运算

    通常情况下计算除法会使用 div/idiv 这两条指令,该指令分别用于计算无符号和有符号除法运算,但除法运算所需要耗费的时间非常多,大概需要比乘法运算多消耗10倍的CPU时钟,在Debug模式下,除法运算不会被优化,但Release模式下,除法运算指令会被特定的算法经过优化后转

    2024年02月11日
    浏览(33)
  • 5.7 汇编语言:汇编高效乘法运算

    乘法指令是一种在CPU中实现的基本算术操作,用于计算两个数的乘积。在汇编语言中,乘法指令通常是通过 mul(无符号乘法) 和 imul(有符号乘法) 这两个指令实现的。由于乘法指令在执行时所消耗的时钟周期较多,所以编译器在优化代码时通常会尝试将乘法操作转换为更

    2024年02月11日
    浏览(21)
  • 汇编语言第一讲:计算机的组织架构和汇编语言介绍

    第一讲:计算机的组织架构和汇编语言介绍 汇编语言 计算机组织架构 数字电路 术语回顾 数制 数字电路 硬件电路 数字电路的问题 汇编语言的开始 程序的节(sections) 调用操作系统的系统调用 列出文件(Listing files) 汇编和链接 调试汇编程序 反汇编现有的程序 附录 课程资源

    2024年04月09日
    浏览(38)
  • 5.10 汇编语言:汇编过程与结构

    过程的实现离不开堆栈的应用,堆栈是一种后进先出 (LIFO) 的数据结构,最后压入栈的值总是最先被弹出,而新数值在执行压栈时总是被压入到栈的最顶端,栈主要功能是暂时存放数据和地址,通常用来保护断点和现场。 栈是由 CPU 管理的线性内存数组,它使用两个寄存器 (S

    2024年02月11日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包