使用 GNU 汇编语法编写 Hello World 程序的三种方法

news2024/10/7 14:32:28

本篇我们使用汇编来写一个经典的 Hello world 程序。

运行环境:

  • OS:Ubuntu 18.04.5 LTS x86-64
  • gcc:version 4.8.5

在用户空间编写汇编程序输出字符串,有三种方法:

  1. 调用C库函数 printf
  2. 使用软中断 int 0x80
  3. 使用 syscall系统调用

下面对三种方法,分别进行说明。

一、调用c库函数

为了更好的理解汇编代码,我们先介绍下 x86-64 架构中函数调用的习惯。

1.1 x86-64架构中函数调用的习惯

1.1.1 参数传递

x86-64中,最多允许 6 个参数通过通用寄存器来传递,多出的参数需要通过栈来传递;传递参数时,参数的顺序与寄存器的关系对应如下:

操作数大小(位)参数1参数2参数3参数4参数5参数6
64%rdi%rsi%rdx%rcx%r8%r9
32%edi%esi%edx%ecx%r8d%r9d
16%di%si%dx%cx%r8w%r9w
8%dil%sil%dl%cl%r8b%r9b

当参数大于 6 个时,把超出的参数放到栈上,而参数 7 位于栈顶。

1.1.2 返回值

被调用函数返回时,把返回结果放入 %rax中,供调用函数来获取。

1.1.3 栈对齐

根据 System V AMD64 ABI 文档(下文简称 ABI 文档)说明(第 3.2.2 The Stack Frame 节),在 发起 call 指令之前,栈需要是16字节对齐的。

The end of the input argument area shall be aligned on a 16 (32 or 64, if __m256 or
__m512 is passed on stack) byte boundary. 11 In other words, the stack needs to be 16 (32
or 64) byte aligned immediately before the call instruction is executed.

1.1.4 XMM寄存器

根据 ABI 文档说明(第 3.2.3 Parameter Passing 节),当被调用的函数中有浮点数时,需要使用 %xmm0~%xmm7 共 8 个 SSE 寄存器来传递参数;

If the class is SSE, the next available vector register is used, the registers are taken
in the order from %xmm0 to %xmm7.

另外需要使用 %al 寄存器来指定使用的矢量寄存器的最大数量。

For calls that may call functions that use varargs or stdargs (prototype-less calls or calls
to functions containing ellipsis (. . . ) in the declaration) %al is used as hidden argument
to specify the number of vector registers used. The contents of %al do not need to match
exactly the number of registers, but must be an upper bound on the number of vector
registers used and is in the range 0–8 inclusive.

x86-64函数调用习惯,也可以参考维基百科上的文档,地址在这里:System V AMD64 ABI ;另外,关于 ABI 最新文档,可以从这里获取:x86-64-ABI 。

1.2、打印 Hello world!

代码如下:

.section .data
​
msg:
    .asciz "Hello world!\n"     # 定义了字符串 'Hello world!',由于是使用.asciz 定义的,会自动在字符串后面加上字符 '\0',以满足 C 语言习惯。
​
.section .text
.globl main
main:
    /* 调用 printf() 函数打印 "Hello world!" */
    /*  printf函数原型:int printf(char *fmt, ...) */
    subq $8, %rsp       # 发起 CALL 调用之前,栈必须是对齐到16字节,否则会报 segment fault 错误
    xorq %rax, %rax     # 被调用函数参数中有浮点数时, %al 寄存器中保存的是需要传送到 XMM 寄存器的参数数量
    mov $msg, %rdi      # 字符串地址
    call printf         # 调用C库函数 printf
    
    /* return */
    xorq %rax, %rax     # main函数返回值,rax = 0
    addq $8, %rsp       # 恢复原来的栈地址
    ret                 # 从 main 函数返回

编译并运行:

$ gcc -o helloworld helloworld.s
$ ./helloworld
Hello world!
$ echo $?
0

需要说明的是,我们在程序运行完成后,使用 echo $?来检查函数的返回值,这个返回值就是我们调用 ret指令之前,%rax里保存的值。我们可以把%rax里的值改成改成其它值,比如说 100(movq $100, %rax) 来验证下。

内核资料领取, Linux内核源码学习地址。

1.3 打印包含浮点数的格式化字符串

上面举了个最简单的输出 Hello world 的例子,如果说我们输出的参数里有变量,而且是个浮点数,该如何处理呢?根据函数调用习惯,我们把代码稍微修改一下,让它可以打印出 Hello world!1234.56,并且让函数返回100:

.section .data
​
msg:
    .asciz "Hello world!%.2f\n"
f:
    .double 1234.56
​
.section .text
.globl main
main:
    /* 调用 printf() 函数打印 "Hello world!" */
    /*  printf函数原型:int printf(char *fmt, ...) */
    subq $8, %rsp       # 发起 CALL 调用之前,栈必须是对齐到16字节,否则汇报 segment fault 错误
    movl $1, %eax       # 被调用函数参数中有浮点数时, %al寄存器中保存的是需要传送到XMM寄存器的参数数量,我们传入了1个浮点数,所以为1
    mov $msg, %rdi      # 字符串地址
    movsd f, %xmm0      # 参数为浮点数时,需要使用%xmm系列寄存器来传参
    call printf         # 调用C库函数 printf
    
    /* return */
    movq $100, %rax     # main函数返回值,rax = 100
    addq $8, %rsp       # 恢复原来的栈地址
    ret  

编译并运行:

$ gcc -o helloworld helloworld.s 
$ ./helloworld
Hello world!1234.56
$ echo $?
100

可以看到,运行后输出了浮点数,且返回值为100。

二、应用程序、C库和内核之间的关系

调用 printf() 函数时,应用程序、C库和内核之间的关系如下图所示:

从图中可以看到,我们调用C库函数printf()时,最终会调用内核的write()系统调用,那么我们就可以绕过C库,直接使用系统调用来输出字符串。

在Linux/x86 系统上,系统调用可以通过多种方式来实现。在32位系统上,可以通过 int 0x80sysenter来实现;在64位系统上,使用syscall来实现。其中 int 0x80是传统的系统调用方式,被称为 legacy system callsysentersyscall是后来添加的指令,被称为 Fast System Call 。

三、软中断 int 0x80

3.1 参数传递

当使用 int 0x80进行系统调用时,参数与寄存器的对应关系如下图所示:

系统调用号参数1参数2参数3参数4参数5参数6
%rax%rbx%rcx%rdx%rsi%rdi%rbp

该对应关系可以从 linux kernel 源码arch/x86/entry/entry_32.S里找到。如果大家不方便下载源码,可以从源码阅读网站查看,各版本的内核源码都有,地址在这里:Linux kernel在线阅读网站。

下面是5.0版本内核文件里的调用参数介绍,文档地址。

/*
 * 32-bit legacy system call entry.
 *
 * 32-bit x86 Linux system calls traditionally used the INT $0x80
 * instruction.  INT $0x80 lands here.
 *
 * This entry point can be used by any 32-bit perform system calls.
 * Instances of INT $0x80 can be found inline in various programs and
 * libraries.  It is also used by the vDSO's __kernel_vsyscall
 * fallback for hardware that doesn't support a faster entry method.
 * Restarted 32-bit system calls also fall back to INT $0x80
 * regardless of what instruction was originally used to do the system
 * call.  (64-bit programs can use INT $0x80 as well, but they can
 * only run on 64-bit kernels and therefore land in
 * entry_INT80_compat.)
 *
 * This is considered a slow path.  It is not used by most libc
 * implementations on modern hardware except during process startup.
 *
 * Arguments:
 * eax  system call number
 * ebx  arg1
 * ecx  arg2
 * edx  arg3
 * esi  arg4
 * edi  arg5
 * ebp  arg6
 */

3.2 系统调用号

在 x86-64 系统上,虽然仍然可以使用 int 0x80 来进行系统调用,但它执行的是32位的系统调用,使用的是32位的系统调用表,且效率低下,不应该再使用;在64位系统上,应该使用syscall系统调用,来使用64位的系统调用表。

32位系统调用表,可以在这里获取。下面列出了32位系统的部分调用及编号,可以看到,write()的系统调用编号为 4 ,exit()系统调用编号为 1。

#
# 32-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point> <compat entry point>
#
# The __ia32_sys and __ia32_compat_sys stubs are created on-the-fly for
# sys_*() system calls and compat_sys_*() compat system calls if
# IA32_EMULATION is defined, and expect struct pt_regs *regs as their only
# parameter.
#
# The abi is always "i386" for this file.
#
0   i386    restart_syscall     sys_restart_syscall
1   i386    exit            sys_exit
2   i386    fork            sys_fork
3   i386    read            sys_read
4   i386    write           sys_write
5   i386    open            sys_open            compat_sys_open
6   i386    close           sys_close
7   i386    waitpid         sys_waitpid
8   i386    creat           sys_creat
9   i386    link            sys_link
10  i386    unlink          sys_unlink
11  i386    execve          sys_execve          compat_sys_execve
​
......

3.3 函数原型

write()系统调用,函数原型:

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

exit()系统调用,函数原型:

void _exit(int status);

3.4 汇编代码

.section .data
​
msg:
    .ascii "Hello world!\n"
len = . - msg
​
.section .text
.globl main
main:
    /* write(2) 系统调用, 打印 "Hello world!" */
    /* write(2)原型:ssize_t write(int fd, const void *buf, size_t count); */
    movq $4, %rax       # write()系统调用号,4
    movq $1, %rbx       # 第一个参数,fd
    movq $msg, %rcx     # 第二个参数,buf
    movq $len, %rdx     # 第三个参数,count
    int $0x80
​
    /* exit(2) 系统调用  */
    /* exit()原型:void _exit(int status); */
    movq $1, %rax       # exit()系统调用号,1
    movq $0, %rbx       # 状态码,status
    int $0x80

编译并执行:

$ gcc -o helloworld helloworld.s 
$ ./helloworld
Hello world!
$ echo $?
0

说明:

  • 这里使用了.ascii 来定义一个字符串,而没有使用 .asciz,是因为我们不再需要兼容C的习惯,我们需要自己计算字符串的长度。
  • len = . - msg 里, ”.“表示当前地址。

四、syscall系统调用

4.1 参数传递

当使用 syscall进行系统调用时,参数与寄存器的对应关系如下图所示:

系统调用号参数1参数2参数3参数4参数5参数6
%rax%rdi%rsi%rdx%r10%r8%r9

该对应关系可以从 linux kernel 源码 arch/x86/entry/entry_64.S 里找到。下面是 5.0 版本内核文件里的调用参数介绍,文档地址。

/*
 * 64-bit SYSCALL instruction entry. Up to 6 arguments in registers.
 *
 * This is the only entry point used for 64-bit system calls.  The
 * hardware interface is reasonably well designed and the register to
 * argument mapping Linux uses fits well with the registers that are
 * available when SYSCALL is used.
 *
 * SYSCALL instructions can be found inlined in libc implementations as
 * well as some other programs and libraries.  There are also a handful
 * of SYSCALL instructions in the vDSO used, for example, as a
 * clock_gettimeofday fallback.
 *
 * 64-bit SYSCALL saves rip to rcx, clears rflags.RF, then saves rflags to r11,
 * then loads new ss, cs, and rip from previously programmed MSRs.
 * rflags gets masked by a value from another MSR (so CLD and CLAC
 * are not needed). SYSCALL does not save anything on the stack
 * and does not change rsp.
 *
 * Registers on entry:
 * rax  system call number
 * rcx  return address
 * r11  saved rflags (note: r11 is callee-clobbered register in C ABI)
 * rdi  arg0
 * rsi  arg1
 * rdx  arg2
 * r10  arg3 (needs to be moved to rcx to conform to C ABI)
 * r8   arg4
 * r9   arg5
 * (note: r12-r15, rbp, rbx are callee-preserved in C ABI)
 *
 * Only called from user space.
 *
 * When user can change pt_regs->foo always force IRET. That is because
 * it deals with uncanonical addresses better. SYSRET has trouble
 * with them due to bugs in both AMD and Intel CPUs.
 */

4.2 系统调用号

64位系统调用表,可以在这里获取。下面列出了64位系统的部分调用及编号,可以看到,write()的系统调用编号为 1 ,exit()系统调用编号为 60。

#
# 64-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point>
#
# The __x64_sys_*() stubs are created on-the-fly for sys_*() system calls
#
# The abi is "common", "64" or "x32" for this file.
#
0   common  read            sys_read
1   common  write           sys_write
2   common  open            sys_open
3   common  close           sys_close
4   common  stat            sys_newstat
5   common  fstat           sys_newfstat
6   common  lstat           sys_newlstat
7   common  poll            sys_poll
8   common  lseek           sys_lseek
9   common  mmap            sys_mmap
10  common  mprotect        sys_mprotect
​
......
  
55  64  getsockopt      sys_getsockopt
56  common  clone           sys_clone
57  common  fork            sys_fork
58  common  vfork           sys_vfork
59  64  execve          sys_execve
60  common  exit            sys_exit
61  common  wait4           sys_wait4
62  common  kill            sys_kill
63  common  uname           sys_newuname
64  common  semget          sys_semget
65  common  semop           sys_semop
​
......

4.3 函数原型

write()系统调用,函数原型:

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

exit()系统调用,函数原型:

void _exit(int status);

4.4 汇编代码

.section .data
msg:
    .ascii "Hello World!\n"
len = . - msg
​
.section .text
.globl  main
main:
​
    # ssize_t write(int fd, const void *buf, size_t count)
    mov $1, %rdi            # fd
    mov $msg, %rsi          # buffer
    mov $len, %rdx          # count
    mov $1, %rax            # write(2)系统调用号,64位系统为1
    syscall
​
    # exit(status)
    mov $0, %rdi            # status
    mov $60, %rax           # exit(2)系统调用号,64位系统为60
    syscall

编译并运行:

$ gcc -o helloworld helloworld.s 
$ ./helloworld
Hello world!
$ echo $?
0
提示
同样的系统调用函数,在32位系统和64位系统里,其调用号是不一样的,因为使用的是不同的系统调用表。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/569201.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java的CookieManager

文章目录 1. 简介2. CookieStore 1. 简介 Java5包括一个抽象类Java.net.CookieHandler&#xff0c;它定义了存储和获取Cookie的一个API&#xff0c;但不包括这个抽象类的实现&#xff0c;所以还有很多工作要做。Java6进一步作了补充&#xff0c;为CookieManager增加了一个可以…

领导让我搭建Appium环境,还好我看到了这篇文章

首先介绍一下&#xff0c;Appium是一个APP的自动化框架&#xff0c;可用于测试APP、网页(web)、混合型应用&#xff0c;而且是跨平台(可以针对不同平台用一套api来编写测试用例)的。 pythonAppium自动化测试框架【项目实战合集】&#xff0c;轻松掌握app高级自动化测试_哔哩哔…

Redis 的数据类型和命令帮助

文章结构 Redis 数据类型1. Redis全局命令&#xff08;跟key有关系&#xff0c;而跟value无关&#xff09;2. StringsGetting and setting StringsManaging counters 3. Lists(L)Basic commandsBlocking commands 4. Sets(S)Basic commands 5. Hashes(H)Basic commands 6. Sort…

2.VirtualBox安装CentOS 7

安装VirtualBox 到https://www.virtualbox.org/wiki/Downloads下载并且安装&#xff0c;请选择对应系统的版本进行安装&#xff0c;我是Mac OS。一路Next。 下载CentOS虚拟镜像 到https://www.osboxes.org/centos/下载CentOS的虚拟镜像。我下载的是CentOS 7&#xff0c;64bi…

从零搭建微服务-认证中心(一)

写在最前 如果这个项目让你有所收获&#xff0c;记得 Star 关注哦&#xff0c;这对我是非常不错的鼓励与支持。 源码地址&#xff1a;https://gitee.com/csps/mingyue 文档地址&#xff1a;https://gitee.com/csps/mingyue/wikis 技术选型 本微服务将采用 Sa-Token 作为权限…

六级备考24天|CET-6|翻译技巧12|理解背诵|11:00~12:00+14:20~15:30

目录 一、翻译难点 二、评分标准​ 三、六级翻译之词汇 中国特色词汇 除夕 元宵节 上元节/灯节 年夜饭 团圆饭 大扫除/彻底打扫 不畏艰难 砥砺前行 四、六级翻译之语法 &#xff08;一&#xff09;定语从句 使用定语从句的前提 1. 带有动词的“的”结构 2. 相邻句子有名词重复 …

VMware重新安装VMwareTool字体为灰色情况+ubuntu时间设置

文章目录 前言&#xff1a;1. 重新安装VMwareTool字体为灰色2. VMware下ubuntu的时间设置 前言&#xff1a; 之前退出VMware关闭的时候没有等待虚拟机的状态保存&#xff0c;强制关机了。这就导致后面使用的时候&#xff0c;共享目录无法显示情况。对于上面的情况我的博客里面…

无毛刺时钟切换电路

为了SOC设计的低功耗性&#xff0c;多时钟域的划分是常用手段之一&#xff0c;有两个时钟&#xff0c;A为50Mhz&#xff0c;B为100Mhz&#xff0c;请设计无毛刺时钟切换电路&#xff0c;根据控制信号control&#xff0c;输出所需时钟信号。 control信号至少对一个时钟信号为异…

SpringCloud Sleuth+Zipkin

SpringCloud SleuthZipkin 官网 https://github.com/spring-cloud/spring-cloud-sleuth Sleuth/Zipkin 是什么&#xff1f; 概述(两张图) 在微服务框架中&#xff0c;一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用, 来协同产生最后的请求结果&#x…

WhatsApp如何推动更多销售额

WhatsApp在全球拥有超过2亿用户&#xff0c;已成为最突出的通信应用程序。对于企业来说&#xff0c;这提供了一个有利可图的机会&#xff0c;可以大规模推广他们的产品和服务&#xff0c;而成本可能只是传统营销成本的一小部分。 如果您拥有电子商务业务&#xff0c;WhatsApp可…

《逆商》我们该如何应对坏事件

关于作者 作者保罗史托兹博士是逆商理论的提出者和奠基人&#xff0c;他曾被《人力资源》杂志评为 “全球十大有影响力的思想家”。在二十多年前提出逆商理论之后&#xff0c;他一直在致力于帮助各行各业的人士提高逆商&#xff0c;在实践中积累了该领域大量的数据和经验。 关…

典型环节的仿真实验(自控实验一)

实验目的 1&#xff09;了解MATLAB Simulink的组成&#xff1b; 2&#xff09;掌握线性系统Simulink仿真模型的建立方法&#xff1b; 3&#xff09;通过典型环节阶跃响应的仿真&#xff0c;了解典型环节的动态特性&#xff1b; 4&#xff09;了解MATLAB进行线性系统仿真的相…

2023新版Spring6全新讲解-核心内容之IoC

Spring核心之IoC 一、IoC概念介绍 1.IoC 介绍 IoC 是 Inversion of Control 的简写&#xff0c;译为“控制反转”&#xff0c;它不是一门技术&#xff0c;而是一种设计思想&#xff0c;是一个重要的面向对象编程法则&#xff0c;能够指导我们如何设计出松耦合、更优良的程序。…

如何获得铁粉?

铁粉是指那些非常喜欢某个人、品牌、产品或组织的人群&#xff0c;他们对所支持的对象非常忠诚&#xff0c;会在各种场合下宣传、支持和捍卫他们的偶像。铁粉通常会在社交媒体上关注他们的偶像、分享他们的内容、购买他们的产品或服务&#xff0c;并与其他铁粉互动和交流。他们…

JavaScript中的生成器函数(Generator Functions)

简介&#xff1a; 生成器函数&#xff08;Generator Functions&#xff09;是JavaScript中的一种特殊类型函数&#xff0c;它允许开发者在函数内部产生多个值并逐步返回&#xff0c;与传统函数一次返回单个值的方式不同。本文将介绍生成器函数的特性、优缺点以及如何使用它们&a…

【重新定义matlab强大系列十】函数normalize进行归一化数据

&#x1f517; 运行环境&#xff1a;Matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

2. css表格属性、文本属性、列表属性、边距属性、尺寸属性

1. 表格属性 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width…

每日一题——四数之和(双指针解法)

每日一题 四数之和 注&#xff1a; 如果大家没做过题目两数之和、三数之和&#xff0c;强烈建议先去做做&#xff0c;也可以参考我之前写的博客&#xff0c;这样做这一题会事半功倍&#xff0c;且由于本题思路和三数之和十分类似&#xff0c;故对于解题思路&#xff0c;也不会…

涨点神器:注意力机制---多头上下文集成(Context Aggregation)的广义构建模块,助力小目标检测,暴力涨点

1.数据集性能验证 在crack道路缺陷检测任务中,多头上下文集成(Context Aggregation)的广义构建模块实现暴力涨点mAP50从0.954提升至0.992 🏆🏆🏆🏆🏆🏆Yolov5/Yolov7魔术师🏆🏆🏆🏆🏆🏆 ✨✨✨魔改网络、复现前沿论文,组合优化创新 🚀🚀🚀…

第一章.The Learning Problem

第一章.The Learning Problem 1.1 The Learning Problem 1.机器学习的概念&#xff1a; 机器学习就是机器从数据中总结经验。从数据中找出某种规律或者模型&#xff0c;并用他来解决某种实际问题。 2.机器学习的应用场景 1).事物本身存在某种潜在规律 2).某些问题难以使用普…