不一样的hello world

Linux 的系统调用

  • 通过glibc提供的库函数

    glibc 是 Linux 下使用的开源的标准 C 库,它是 GNU 发布的 libc 库,即运行时库。glibc 为程序员提供丰富的 API,除了例如字符串处理、数学运算等用户态服务之外,最重要的是封装了操作系统提供的系统服务,即系统调用的封装。
  • 使用syscall直接调用

    该函数定义在 unistd.h 头文件中,函数原型如下:
    
    long int syscall (long int sysno, ...)
    
    1. sysno 是系统调用号,每个系统调用都有唯一的系统调用号来标识。
    2. ... 为剩余可变长的参数,为系统调用所带的参数。
    3. 返回值 该函数返回值为特定系统调用的返回值,在系统调用成功之后你可以将该返回值转化为特定的类型,如果系统调用失败则返回 -1。
  • 通过int指令陷入

    用户态程序通过软中断指令`int 0x80` 来陷入内核态,参数的传递是通过寄存器,eax 传递的是系统调用号,ebx、ecx、edx、esi和edi 来依次传递最多五个参数,当系统调用返回时,返回值存放在 eax 中。
    【系统调用号可在 /usr/include/asm/unistd_32.h(unistd_64.h)中查询 】

系统调用函数

write 调用

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

write 调用的调用号为4,eax=4
fd 表示被写入的文件句柄,这里要向终端输出,文件句柄为1,ebx=1
buf 表示要写入的缓冲区地址,ecx=str 【hello world!字符串】
size 表示要写入的字节数,edx=13 【hello world!】

exit 调用

void exit(int status);

exit 调用的调用号为1。
status 表示进程退出码,如我们平时的main程序return的数值会返回给系统库,由系统库将该数值传递给exit系统调用。

有了上面的铺垫我们就可以做到,写一个不一样的hello world 程序,并且做到以下要求:

  • 不调用任何系统库
  • 不使用main函数【结束进程使用系统调用函数exit】

代码实现

GCC/GNU 编译器和 Clang/LLVM 编译器默认使用 AT&T/UNIX 汇编语法, GCC 可以通过加参数 -masm=intel 来使用 Intel 汇编语法,该参数并不适用于 Clang。

这里使用 Intel 语法 【AT&T/UNIX 语法 相应修改即可】

/* helloworld.c */   

char * str="hello world\n";

void print(){

	__asm(
"mov edx,13\n\t" //字符串长度
"mov ecx,str\n\t" //待显示字符串
"mov ebx,1\n\t" //文件描述符(stdout)
"mov eax,4\n\t" //write 系统调用号
"int 0x80\n\t" //软中断指令 进入系统调用
);
} void end(){ __asm(
"mov ebx,0\n\t" //进程退出码
"mov eax,1\n\t" //exit 系统调用号
"int 0x80\n\t"
);
} void run()
{
print();
end();
}

进行编译

# -masm=intel Intel 语法
# -fno-builtin 关闭gcc内置函数功能
# 生成可重定向文件 gcc helloworld.c -c -masm=intel -fno-builtin # -static 让ld使用静态链接方式链接程序
# -e 程序入口
# 生成可执行文件 ld -static -e run -o helloworld helloworld.o

可以看到成功生成可执行文件!

.text 保存的是程序的指令,它是只读的。
.rodata 保存的是字符串“HelloWorld!\n”,它也是只读的。
.data 保存的是Sir全局变量,看上去它是可读写的,但我们并没有在程序中改写该变量,所以实际上它也是只读的。
.comment保存的是编译器和系统版本信息,这些信息也是只读的。由于.comment里面保存的数据并不关键,对程序的运行没有作用,所以可以丢弃。 .eh_frame 可以通过把代码写入.eh_frame中(覆盖其原来的内容)可以实现binary大小基本没有变化。若存在该段,我们能够进行改写并无影响。
【但这个段也极大增加了elf文件的大小】

鉴于这些段的属性如此相似,原则上讲,我们可以把它们合并到一个段里面,该段的属性是可执行、可读的,包含程序的数据和指令。为了达到这个目的,我们使用Id链接脚本来控制链接过程。

ld链接脚本
/* tinyhelloworld.lds */

ENTRY(run)

SECTIONS
{
. = SIZEOF_HEADERS; /* = 旁的空格很关键,不然也会报错 */
.tinytext : {*(.text) *(.rodata) *(.data)}
/DISCARD/ : {*(.comment) *(.eh_frame)}
} # "."表示当前虚拟地址
# SIZEOF_HEADERS 输出文件头的大小
# tinytext: 新合并段名称
# /DISCARD/: 丢弃其中段

链接命令

ld -static -T tinyhelloworld.lds -o tinyhelloworld helloworld.o -s 

# 注意:这里 -T -o 的顺序不当,会引起报错
# -s 去除符号

可以看到,除了重定向表外,就只有.tinytext段了。并且可执行程序的大小为576字节

《程序员的自我修养》学习笔记——不一样的hello world【第四弹】的更多相关文章

  1. pwn学习日记Day19 《程序员的自我修养》读书笔记

    windows PE/COFF章总结 本章学习了windows下的可执行文件和目标文件格式PE/COFF.PE/COFF文件与ELF文件非常相似,它们都是基于段的结构的二进制文件格式.Windows下 ...

  2. pwn学习日记Day21 《程序员的自我修养》读书笔记

    Linux内核装载ELF过程 (1)bash进程调用fork()系统调用创建一个新的进程 (2)新的进程调用execve()系统调用执行指定的ELF文件,原先的bash进程继续返回等待刚才启动的新进程 ...

  3. pwn学习日记Day20 《程序员的自我修养》读书笔记

    可执行文件的装载与进程 覆盖装入和页映射是两种典型的动态装载方法 进程建立的三步 1.创建一个独立的虚拟地址空间 2.读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系. 3.将CPU的指令寄存 ...

  4. 第八周读书笔记(人月神话X月亮与六便士)——到底什么才是一个程序员的自我修养?

    写了这么久的读书笔记,涉及到问题大多是一些如何把软件工程做好,如何把自己的职业生涯做好.但总感觉逻辑链上缺了一环,亦即:我们为什么要把软件工程做好,我们成为一名优秀的职业生涯的意义到底在于什么?我觉得 ...

  5. gcc ld 链接器相关知识,调试指令(程序员的自我修养----链接、装载与库)

    最近解决一个动态链接上的问题,因为以前从来没有接触过这方面的知识,所以恶补了一下,首先要了解gcc编译指令(makefile),ld链接器的选项(还有连接脚本section指定内存位置),熟悉查看连接 ...

  6. 程序员的自我修养(2)——计算机网络(转) good

    相关文章:程序员的自我修养——操作系统篇 几乎所有的计算机程序,都会牵涉到网络通信.因此,了解计算机基础网络知识,对每一个程序员来说都是异常重要的. 本文在介绍一些基础网络知识的同时,给出了一些高质量 ...

  7. 程序员的自我修养:高效使用Google解决问题

    如果票选近二十年最伟大的发明,我相信搜索引擎肯定会占据一个不容小觑的位置,它不单是一项发明,更是一项成就,最大程度消灭了信息的不平等.既然人人都可以接触到海量的信息,那么衡量信息财富多寡就只剩下技巧这 ...

  8. pwn学习日记Day18 《程序员的自我修养》读书笔记

    知识杂项 obj文件:当前源代码编译成二进制目标文件 exe文件:将.obj文件与库文件.lib等文件链接生成的可执行文件 一个现代编译器的主要工作流程如下: 源程序(source code)→ 预处 ...

  9. 《程序员的自我修养》读书笔记——系统调用、API

        系统调用 程序运行的时候,本身是没有权限访问多少系统资源的.系统资源有限,如果操作系统不进行控制,那么各个程序难免会产生冲突.线程操作系统都将可能产生冲突的系统资源保护起来,阻止程序直接访问. ...

  10. Java程序员的自我修养

    一.自我修养路线图 如图,这是笔者所走的路.且不论这路走的对不对,这个过程中行业环境会影响到你,大可不必钻牛角尖.附上这张图的目的是为了说,如果你想成为一个优秀的程序员,那么你一定要有规划.当然,别想 ...

随机推荐

  1. Coursera Programming Languages, Part B 华盛顿大学 Week 1

    来上 programming language 的第二 part 了!这一部分介绍的语言是 Racket,之前就听说过它独特的括号语法,这次来具体了解一下 Racket definitions, fu ...

  2. 1.2 C语言--函数与数组

    函数 函数的定义 返回值类型函数名(类型形参名[,--]){ 函数体 } 除了没有访问修饰符外,基本等同于java的函数. 良好的程序设计风格要求即使没有返回值,也要使用return;作为最后一条语句 ...

  3. <雪山飞狐><飞狐外传 >合辑剧情+随笔

    严格而言雪山飞狐与飞狐外传的剧情并不相关,前者写作与前,然后飞狐外传算是对雪山飞狐中形象并不饱满的胡斐作进一步补充描述,同时对二十余年前苗人凤与胡一刀之间故事的补充,以及众人叙述中的一些补充.因此虽然 ...

  4. Error running 'tm8': Cannot load C:\Users\Administrator\.IntelliJIdea2019.3\system\tomcat\Unnamed_jymes_3\conf\server.xml (系统找不到指定的文件。)

    救命救命,由于脑壳被门夹了去更改了idea的配置,导致重启项目报错!又是给自己挖坑的一天,唉!!! 主要是看报错信息还一直以为是tomcat的问题,然后试了很多方法,比如查看配置的tomcat路径.重 ...

  5. 【Leetcode第286场周赛】——周赛总结

    1.5268. 找出两数组的不同 - 力扣(LeetCode) (leetcode-cn.com) 给你两个下标从 0 开始的整数数组 nums1 和 nums2 ,请你返回一个长度为 2 的列表 a ...

  6. CMD输出文本文件内容的type指令(替代Linux的cat指令)

    :: 本脚本的所有指令本身不输出到屏幕 @echo off :: 打印文本文件内容并匹配关键字,结果不输出到屏幕 type result.txt | find "error" &g ...

  7. Python 爬虫代码应该怎么写?

    对于入行已久的老程序员也并不一定精通爬虫代码,这些需要时间的沉淀还需要更多的实战案例,简单的问句你真的会写爬虫么?下面就是我日常写的一个y文件加上几个请求并且把需要的功能全部实现模块化,可以让我们爬虫 ...

  8. mapreduce启动命令

    mapreduce启动命令 hadoop jar /var/lib/hadoop-hdfs/codejar/flash_format_testip.jar com.js.dataclean.hm2_h ...

  9. Linux CentOS Docker Asp.net Core MVC 模板项目的部署

    本文只是记录 .net core 3.1 项目的基本部署方法,灵活应对不同情况与需求 工具:VS 2019    Linux CentOS 7 x64 Xshell     Xftp Docker 安 ...

  10. 【jupyter notebook】配置基于ecs云平台的jupyter notebook远程使用

    学校提供了ecs云服务器进行高性能运算 分配到了云主机后想在上面搭好jupyter notebook服务器方便远程使用,在此记录整个流程和遇到的问题 本文档适用于常规的jupyter notebook ...