文件压缩-42的魅力

news2024/9/24 13:25:39

让我们以一个非常简单的程序为例,一个什么都不做的程序 将数字返回给操作系统。为什么不呢?毕竟,Unix 已经附带了不少于两个这样的程序:true 和 假。由于已经取了 0 和 1,我们将使用数字 42。

所以,这是我们的第一个版本:

 /* tiny.c */
  int main(void) { return 42; }

我们可以像这样编译和测试:

 $ gcc -Wall tiny.c
 $ ./a.out ; echo $?//返回返回值
  42

图示操作如下:

所以。它有多大?它有多大,好吧,在我的机器上,我得到:

  $ wc -c a.out
     3998 a.out
(你的可能会有所不同。诚然,这是相当小的 今天的标准,但几乎可以肯定比它需要的要大。

 我的机器上结果如下:

 显而易见的第一步是剥离可执行文件:

 这当然是一个进步。对于下一步,怎么样 优化?

这在理论上也有所帮助,但实际上:几乎没有任何需要优化的东西。 

我们似乎不太可能做太多其他事情来缩小 单语句 C 程序。我们将不得不把 C 抛在后面,然后 请改用汇编程序。希望这将减少所有额外的内容 C 程序自动产生的开销。

所以,进入我们的第二个版本。我们需要做的就是从 main() 中。在汇编语言中,这意味着函数应设置 累加器 EAX 设置为 42,然后返回:

首先建立.asm文件

gedit tiny.asm

内容如下: 

 ; tiny.asm
  BITS 32
  GLOBAL main
  SECTION .text
  main:
                mov     eax, 42
                ret

 编译指令如下:

 $ nasm -f elf tiny.asm
 $ gcc -Wall -m32 -s tiny.o
 $ ./a.out ; echo $?
 42

结果如下:

这里可以注意,编译的时候报了一个错误:

问题原因:
在64位系统下去编译32位的目标文件,这样是非法的。

解决方案:
用”-m32”强制用32位ABI去编译,即可编译通过。

查看当前大小如下:从14328到13656减少了672. 

 

好吧,问题是我们仍然会产生大量的开销 使用 main() 接口。链接器仍在添加一个接口 我们的操作系统,正是该接口实际上调用了 main()。所以 如果我们不需要它,我们如何解决这个问题?

默认情况下,链接器使用的实际入口点是符号 名称为 _start。当我们与gcc链接时,它会自动 包括一个 _start 例程,一个设置 argc 和 argv 的例程,以及其他 things,然后调用 main()。 

所以,让我们看看我们是否可以绕过它,并定义我们自己的_start 常规

  ; tiny.asm
  BITS 32
  GLOBAL _start
  SECTION .text
  _start:
                mov     eax, 42
                ret

gcc 会做我们想做的事吗?

不。好吧,实际上,是的,它会的,但首先我们需要学习如何提问 为了我们想要的。

碰巧 gcc 识别一个名为 -nostartfiles 的选项。 从 gcc 信息页面:

-nostartfiles
在以下情况下不要使用标准系统启动文件 连接。标准库正常使用。

啊哈!现在让我们看看我们能做些什么:

好吧,gcc 没有抱怨,但该程序不起作用。发生了什么 错?

问题在于我们把_start当作一个 C 函数来对待, 并试图从中返回。实际上,它根本不是一个功能。 它只是链接器用来定位的目标文件中的一个符号 程序的切入点。当我们的程序被调用时,它就会被调用 径直。如果我们看一下,我们会看到顶部的值 堆栈中的数字是 1,这当然非常 un-address-like。事实上,堆栈上的是我们程序的 argc 价值。在此之后是 argv 数组的元素,包括 终止 NULL 元素,后跟 envp 的元素。这就是 都。堆栈上没有返回地址。

那么,_start是如何退出的呢?好吧,它调用了 exit() 函数! 毕竟,这就是它的用途。

实际上,我撒谎了。它真正做的是调用 _exit() 函数。 (请注意前导下划线。exit() 是完成一些 代表流程的任务,但这些任务永远不会 started,因为我们绕过了库的启动代码。所以我们 还需要绕过库的关机代码,直接转到 操作系统的关机处理。

所以,让我们再试一次。我们将调用 _exit(),它是一个 函数,该函数采用单个整数参数。因此,我们需要做的就是 将数字推送到堆栈上并调用该函数。(我们还需要 将 _exit() 声明为外部。这是我们的组装:

  ; tiny.asm
  BITS 32
  EXTERN _exit
  GLOBAL _start
  SECTION .text
  _start:
                push    dword 42
                call    _exit

 查看现在的结果:减少了588字节,变成了13068

嗯......那么还有什么其他的 GCC 有有趣的晦涩选项吗?

好吧,这个,紧跟在 -nostartfiles 之后 文档,当然是引人注目的:

-nostdlib
不要使用标准的系统库和启动链接时的文件。只有您指定的文件才会 传递给链接器。

这值得研究:

哎呀。没错...... 毕竟,_exit() 是一个库函数。 它必须从某个地方填写。

好。但可以肯定的是,我们不需要 libc 的帮助来结束一个程序, 我们呢?

不,我们没有。如果我们愿意抛开所有伪装 可移植性,我们可以使我们的程序退出而不必链接 别的东西。不过,首先,我们需要知道如何制作一个系统 在 Linux 下调用

与大多数操作系统一样,Linux 为 它通过系统调用托管的程序。这包括打开等内容 一个文件,读取和写入文件句柄 - 当然,还有 关闭进程。

Linux 系统调用接口是一条指令:int 0x80。 所有系统调用都通过此中断完成。要进行系统调用, EAX 应包含一个数字,用于指示正在进行哪个系统调用 调用,其他寄存器用于保存参数(如果有)。 如果系统调用采用一个参数,则该参数将位于 ebx 中;一个系统 具有两个参数的调用将使用 EBX 和 ECX。同样,edx、esi 和 如果需要第三个、第四个或第五个参数,则使用 EDI, 分别。从系统调用返回后,eax 将包含 返回值。如果发生错误,eax 将包含一个负值, 绝对值表示错误。

不同系统调用的号码列在 /usr/include/asm/unistd.h。快速浏览一下就会告诉我们,出口 系统调用被分配为数字 1。像 C 函数一样,它需要 一个参数,返回到父进程的值,等等 将进入 EBX。

我们现在知道了创建下一个版本所需的所有信息。 程序,一个不需要任何外部函数帮助的程序 工作

 ; tiny.asm
  BITS 32
  GLOBAL _start
  SECTION .text
  _start:
                mov     eax, 1
                mov     ebx, 42  
                int     0x80

查看结果,从减少了288字节,变成了12780 

所以。。。我们还能做些什么来让它变得更小吗?

使用更短的指令怎么样?

如果我们为汇编代码生成一个列表文件,我们将找到 以后:

 00000000 B801000000        mov        eax, 1
  00000005 BB2A000000        mov        ebx, 42
  0000000A CD80              int        0x80

好吧,哎呀,我们不需要初始化所有 ebx,因为操作 系统将只使用最低的字节。单独设置 bl 将是 足够了,并且将占用两个字节而不是五个字节。

我们还可以通过将 eax 设置为 1 来将其 xor'ing 为零,然后使用 单字节增量指令;这将节省两个字节。

  00000000 31C0              xor        eax, eax
  00000002 40                inc        eax
  00000003 B32A              mov        bl, 42
  00000005 CD80              int        0x80

我认为可以肯定地说,我们不会这样做 编程任何比这更小的程序。

顺便说一句,我们不妨停止使用 gcc 来链接我们的可执行文件, 鉴于我们没有使用它的任何附加功能,而只是 调用链接器 LD,我们自己:

关于为 Linux 创建真正 Teensy ELF 可执行文件的旋风教程 (muppetlabs.com)

后续的内容都是按着这篇文章写得,实现之后实在懒得写博客了。。。。

最后的实现压缩成45字节

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

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

相关文章

AIGC行业:巨头引领的创新浪潮与市场前景

AIGC(AI Generated Content)技术,作为新兴的技术力量,正逐渐改变内容创作的生态。在这一变革中,国内科技巨头如百度、阿里巴巴、腾讯等的积极参与,不仅为行业带来资本和技术支持,更预示着AIGC技…

企业微信H5授权登录

在企业中如果需要在打开的网页里面携带用户的身份信息,第一步需要获取code参数 如何实现企业微信H5获取当前用户信息即accessToken? 1.在应用管理--》创建应用 2.创建好应用,点击应用主页-》设置-》网页-》将授权链接填上去 官方文档可以看…

西安航空学院电子工程学院领导莅临泰迪智能科技参观交流

5月26日,西安航空学院电子工程学院院长杨亚萍、专业教师刘坤莅临广东泰迪智能科技股份有限公司产教融合实训基地参观交流。泰迪智能科技董事长张良均、副总经理施兴、产品中心负责周东平、校企合作经理吴桂锋与泰迪智能科技韩伟进行热情了接待。双方就专业建设、协同…

不仅能逃生,更能自动灭火!神奇的全氟己酮灭火毯的原理是什么?

很多朋友对灭火毯的印象,还停留在火灾发生时披覆在身上逃生时使用,可以隔离火源。近年来兴起的全氟己酮自动灭火毯可以说大为颠覆大家的想法,这是一条真的可以自动灭火的神奇灭火毯!为什么能做到这一点?全氟己酮灭火毯…

(1) 初识QT5

文章目录 Qt Quickdemo信号的命名方式 qml语言一个很重要的概念 qt 模块 Qt Quick Qt Quick是Qt5中⽤户界⾯技术的涵盖。Qt Quick⾃⾝包含了以下⼏种技术: QML-使⽤于⽤户界⾯的标识语⾔JavaScript-动态脚本语⾔Qt C具有⾼度可移植性的C库. 类似HTML语⾔&#xf…

MySQL连表查询练习

– 34. 查询所有员工的姓名和部门名称,没有部门的员工不需要展示 SELECTe.NAME 员工姓名,d.NAME 部门名称 FROMt_emp eINNER JOIN t_dept d ON e.dept_id d.id;– 35. 查询所有员工的姓名和部门名称,没有部门的员工展示BOSS SELECTe.NAME 员工姓名,i…

从华为云Redis到AWS ElastiCache的操作方法

越来越多企业选择出海,那么就涉及到IT系统的迁移,本文将详细介绍如何将华为云Redis顺利迁移到AWS ElastiCache的操作方法,九河云将为您介绍迁移步骤以帮助您顺利完成这一重要任务。 **1. 确定迁移计划** 在开始迁移之前,首先要制…

基于Freertos的工训机器人

一. 工训机器人 V1 1. 实物 将自制的F4开发板放置车底板下方,节省上方空间,且能保证布线方便整齐。 2. SW仿真 使用SolidWorks进行仿真,且绘制3D打印件。 工训仿真 3.3D打印爪测试 机械爪测试 二. 工训机器人 V2 1. 实物 工训机器人V2不同于…

效果炸裂!使用 GPT-4o 快速实现LLM OS

使用 GPT-4o 快速实现LLM OS 什么是 LLM OS?LLM OS 主要有以下5个部分组成: LLM OS 开源实现运行 LLM OS 开源实现 什么是 LLM OS? 关于 LLM OS 的最初构想源自karpathy 在2023年11月11日发布的一条Twitter 动态,这是 LLM OS 概念…

mipi-csi笔记

数据格式 长包,短包 用DI来判断数据类型 测试帧率,如用1G的示波器 下面的代表这是一张图片,用帧间隙来测试YUV422视频的帧率 fps10hz的外同步

JavaScript(ES5) 入门

01-简介 1)JavaScript 发展史 [1] 1995年,navigator(导航者),netscape(网景);用户体验性特别好 [2] 表单验证难题,表单验证都是在后台处理.当时处在网速特别慢的时代,发送一个请求,接收响应 需要5分钟左右的时候提高表单验证的速度,想开发一种语言在前端进行表单验证. [3] 1995…

vue数字翻盘,翻转效果

数字翻转的效果 实现数字翻转的效果上面为出来的样子 下面为代码&#xff0c;使用的时候直接引入&#xff0c;还有就是把图片的路径自己换成自己或者先用颜色替代&#xff0c;传入num和numlength即可 <template><div v-for"(item, index) in processedNums&quo…

电脑怎么恢复刚删除的文件?别急,教你几招

在日常使用电脑的过程中&#xff0c;误删文件的情况时有发生。无论是由于操作失误还是病毒攻击&#xff0c;文件丢失都可能给我们的工作和生活带来不小的困扰。然而&#xff0c;不必过于焦虑&#xff0c;因为在大多数情况下&#xff0c;我们仍然有机会恢复这些丢失的文件。下面…

Linux--构建进程池

目录 1.进程池 1.1.我们先完成第一步&#xff0c;创建子进程和信道 1.2. 通过channel控制&#xff0c;发送任务 1.3回收管道和子进程 1.4进行测试 1.5完整代码 1.进程池 进程池其产生原因主要是为了优化大量任务需要多进程完成时频繁创建和删除进程所带来的资源消耗&#…

windows 执行node报错 800A1391

在项目下执行node -v的时候&#xff0c;抛了这个错误&#xff0c;一开始没发现有啥问题 现在一看&#xff0c;这个报错里的node怎么是个文件... 出现这个问题&#xff0c;是因为项目下&#xff0c;有个同名的文件叫node.js&#xff0c;搞得windows一时不知道是想打开node.js文…

gem5模拟器入门(一)——环境配置

什么是gem5&#xff1f; gem5是一个模块化的离散事件驱动的计算机系统模拟器平台。这意味着&#xff1a; GEM5 的组件可以轻松重新排列、参数化、扩展或更换&#xff0c;以满足您的需求。它将时间的流逝模拟为一系列离散事件。它的预期用途是以各种方式模拟一个或多个计算机系…

数码论坛|基于SprinBoot+vue的数码论坛系统(源码+数据库+文档)

数码论坛系统 目录 基于SprinBootvue的数码论坛系统 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2 管理员功能模块 3 用户后台管理模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&am…

STM32-11-电容触摸按键

STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG STM32-10-定时器 STM32电容触摸按键 电容触摸按键原理&#xff1a; 无手指触摸&#xff1a;上电时&…

DSP开发入门

视频&#xff1a; 创龙TI 最新DSP CPU核心架构 C66x 以及 KeyStone I 架构 DSP TMS320C6655/57以及TMS320C6678视频教程全集_哔哩哔哩_bilibili 2024年硬汉科技手把手教您学DSP28335视频教程持续更新中_哔哩哔哩_bilibili DSP芯片介绍 DSP选型 TI的DSP 分为三大系列&#…

基于Android的家庭理财APP的设计与实现(论文+源码)_kaic

摘 要 随着我国居民收入和生活水平的提高&#xff0c;家庭理财成为人们热议的焦点问题。在需求分析阶段&#xff0c;系统从用户的实际需求出发&#xff0c;确定了用户账户管理、记账、数据分析和提醒功能等几个核心需求。用户账户管理包括用户注册、登录和密码找回等基本操作…