如何编写体积最小的win32程序?本人想听各位的高见!

eggplant 2001-11-20 03:48:01
从代码编写方面,比如使用什么样的API,以及连接什么样的Lib,能减少程序体积?欢迎各位老大的高见。
...全文
1801 52 打赏 收藏 转发到动态 举报
写回复
用AI写文章
52 条回复
切换为时间正序
请发表友善的回复…
发表回复
eggplant 2001-11-27
  • 打赏
  • 举报
回复
看来各位高手的建议是使用汇编,可是这样工作量太大了,如果用VC开发的话,有没有比较通用的方便的招术啊?
eggplant 2001-11-27
  • 打赏
  • 举报
回复
看来各位高手的建议是使用汇编,可是这样工作量太大了,如果用VC开发的话,有没有比较通用的方便的招术啊?
cwanter 2001-11-26
  • 打赏
  • 举报
回复
用汇编吧 跟C一样的
Godsoft 2001-11-26
  • 打赏
  • 举报
回复
关键代码用汇编重新编写
lonecity 2001-11-23
  • 打赏
  • 举报
回复
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib

.data
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is Great!",0

.code
start:
invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start

bighead 2001-11-23
  • 打赏
  • 举报
回复
用ASM编可以只有几K,(而且还可以做点事)
atht 2001-11-23
  • 打赏
  • 举报
回复
宏汇编写的最小框架汇编程序,编译出来只有3K,用C写,无论如何也没有这么小。
jucee 2001-11-23
  • 打赏
  • 举报
回复
等于0算不算最小呢
COE 2001-11-23
  • 打赏
  • 举报
回复
把你的程序COPY到“优盘”里,体积就比较小了
happyboy 2001-11-23
  • 打赏
  • 举报
回复
转篇文章不是win32下的,是Linux下的ELF的


在linux平台上创建超小的ELF可执行文件
整理:alert7(alert7)
来源:http://www.xfocus.org

在linux平台上创建超小的ELF可执行文件

作者:breadbox <breadbox@muppetlabs.com>
原文<A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux >
整理翻译:alert7 <alert7@21cn.com>
主页: http://www.xfocus.org/
时间:2001-9-4

--------------------------------------------------------------------------------


前言:
有些时候,文件的大小是很重要的,从这片文章中,也探讨了ELF文件格式内部的工作
情况与LINUX的操作系统。该片文章向我们展示了如何构造一个超小的ELF可执行文件。

文章中给出的这些example都是运行在intel 386体系的LINUX上。其他系统体系上或许也有同样的
效果,但我不感肯定。

我们的汇编代码使用的是Nasm写的,它的风格类似于X86汇编风格。
NASM软件是免费的,可以从下面得到
http://www.web-sites.co.uk/nasm/



--------------------------------------------------------------------------------

看看下面一个很小的程序例子,它唯一做的事情就是返回一个数值到操作系统中。
UNIX系统通常返回0和1,这里我们使用42作为返回值。

[alert7@redhat]# set -o noclobber && cat > tiny.c << EOF

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

[alert7@redhat]# gcc -Wall tiny.c
[alert7@redhat]# ./a.out ;echo $?
42

再用gdb看看,这个程序实在很简单吧
[alert7@redhat]# gdb a.out -q
(gdb) disass main
Dump of assembler code for function main:
0x80483a0 <main>: push %ebp
0x80483a1 <main+1>: mov %esp,%ebp
0x80483a3 <main+3>: mov $0x2a,%eax
0x80483a8 <main+8>: jmp 0x80483b0 <main+16>
0x80483aa <main+10>: lea 0x0(%esi),%esi
0x80483b0 <main+16>: leave
0x80483b1 <main+17>: ret

看看有多大
[alert7@redhat]# wc -c a.out
11648 a.out

在原作者的机子上3998,在我的rh 2.2.14-5.0上就变成11648,好大啊,我们需要
使它变的更小。

[alert7@redhat]# gcc -Wall -s tiny.c
[alert7@redhat]# ./a.out ;echo $?
42
[alert7@redhat]# wc -c a.out
2960 a.out
现在变成2960,小多了.
gcc -Wall -s tiny.c实际上等价于
gcc -Wall tiny.c
strip a.out 抛弃所有的标号

[alert7@redhat]# wc -c a.out
11648 a.out
[alert7@redhat]# strip a.out
[alert7@redhat]# wc -c a.out
2960 a.out


下一步,我们来进行优化。


[alert7@redhat]# gcc -Wall -s -O3 tiny.c
[alert7@redhat]# wc -c a.out
2944 a.out

我们看到,只比上面的小16个字节,所以以优化指令来减小大小是比较困难的。

很不幸,C程序在编译的时候编译器会增加一些额外的代码,所以接下来我们使用汇编来写程序。

如上一个程序,我们需要返回代码为42,我们只需要把eax设置为42就可以了。程序的
返回状态就是存放在eax中的,从上面一段disass main出来的汇编代码我们也应该知道。

[alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF
; tiny.asm
BITS 32
GLOBAL main
SECTION .text
main:
mov eax, 42
ret
EOF

编译并测试
[alert7@redhat]# nasm -f elf tiny.asm
[alert7@redhat]# gcc -Wall -s tiny.o
[alert7@redhat]# ./a.out ; echo $?
42

现在看看汇编代码有什么不同,看看它的大小
[alert7@redhat]# wc -c a.out
2892 a.out

这样又减小了(2944-2892)52个字节. 但是,只要我们使用main()接口,就还会有许多额外的代码。
linker还会为我们加一个到OS的接口。事实上就是调用main().所以我们如何来去掉我们不需要的
代码呢。

linker默认使用的实际入口是标号_start.gcc联接时,它会自动包括一个_start的例程,设置argc和argv,
....,最后调用main().

所以让我们来看看,是否可以跳过这个,自己定义_start例程。

[alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF
; tiny.asm
BITS 32
GLOBAL _start
SECTION .text
_start:
mov eax, 42
ret
EOF

[alert7@redhat]# nasm -f elf tiny.asm
[alert7@redhat]# gcc -Wall -s tiny.o
tiny.o: In function `_start':
tiny.o(.text+0x0): multiple definition of `_start'
/usr/lib/crt1.o(.text+0x0): first defined here
/usr/lib/crt1.o: In function `_start':
/usr/lib/crt1.o(.text+0x18): undefined reference to `main'
collect2: ld returned 1 exit status


如何做才可以编译过去呢?
GCC有一个编译选项--nostartfiles

-nostartfiles
当linking时,不使用标准的启动文件。但是通常是使用的。

我们要的就是这个,再来:

[alert7@redhat]# nasm -f elf tiny.asm
[alert7@redhat]# gcc -Wall -s -nostartfiles tiny.o
[alert7@redhat]# ./a.out ; echo $?
Segmentation fault (core dumped)
139

gcc没有报错,但是程序core dump了,到底发生了什么?

错就错在我们把_start看成了一个C的函数,然后试着从它返回。事实上它根本不是一个函数。
它仅仅是一个标号,它是被linker使用的一个程序入口点。当程序运行,它也就直接被调用。
假如我们来看,将看到在堆栈顶部的变量值为1,它的确非常的不象一个地址。事实上,在
堆栈那位置是我们程序的argc变量,之后是argv数组,包含NULL元素,接下来是envp环境变量。
所以,那个根本就不是返回地址。

因此,_start要退出,就要调用exit()函数。

事实上,我们实际调用的_exit()函数,因为exit()函数所要做的额外事情太多了,因为我们跳过了
lib库的启动代码,所以我们也可以跳过LIB库的shutdown代码。

好了,再让我们试试。调用_exit()函数,它唯一的参数就是一个整形。所以我们需要push一个数到
堆栈里,然后调用_exit().
(应该这样定义:EXTERN _exit)

[alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF
; tiny.asm
BITS 32
EXTERN _exit
GLOBAL _start
SECTION .text
_start:
push dword 42
call _exit
EOF

[alert7@redhat]# nasm -f elf tiny.asm
[alert7@redhat]# gcc -Wall -s -nostartfiles tiny.o
[alert7@redhat]# ./a.out ; echo $?
42

yeah~~,成功了,来看看多大

[alert7@redhat]# wc -c a.out
1312 a.out

不错不错,又减少了将近一半,:),有没有其他所我们感兴趣的gcc选项呢?

在-nostartfiles就有一个很另人感兴趣的选项:

-nostdlib
在linking的时候,不使用标准的LIB和启动文件。那些东西都需要自己指定传给
linker.
这个值得研究一下:

[alert7@redhat]# gcc -Wall -s -nostdlib tiny.o
tiny.o: In function `_start':
tiny.o(.text+0x6): undefined reference to `_exit'
collect2: ld returned 1 exit status


_exit()是一个库函数,但是加了-nostdlib 就不能使用了,所以我们必须自己处理,
首先,必须知道在linux下如何制造一个系统调用。


--------------------------------------------------------------------------------

象其他操作系统一样,linux通过系统调用来向程序提供基本的服务。
这包括打开文件,读写文件句柄,等等......

LINUX系统调用接口只有一个指令:int 0x80.所有的系统调用都是通过该接口。
为了制造一个系统调用,eax应该包含一个数字(该数字表明了哪个系统调用),其他寄存器
保存着参数。
假如系统调用使用一个参数,那么参数在ebx中;
假如使用两个参数,那么在ebx,ecx中
假如使用三个,四个,五个参数,那么使用ebx,ecx,esi

从系统调用返回时, eax 将包含了一个返回值。
假如错误发生,eax将是一个负值,它的绝对值表示错误的类型。

在/usr/include/asm/unistd.h中列出了不同的系统调用。
快速看一下将看到exit的系统调用号为1。它只有一个参数,该值会返回给父进程,该值会
被放到ebx中。

好了,现在又可以开工了:)

[alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF
; tiny.asm
BITS 32
GLOBAL _start
SECTION .text
_start:
mov eax, 1
mov ebx, 42
int 0x80
EOF

[alert7@redhat]# nasm -f elf tiny.asm
[alert7@redhat]# gcc -Wall -s -nostdlib tiny.o
[alert7@redhat]# ./a.out ; echo $?
42

看看大小

[alert7@redhat]# wc -c a.out
416 a.out


现在可真是tiny,呵呵,那么还能不能更小呢?
如何使用更短的指令呢?

看看下面两段汇编代码:

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


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

很明显从功能上讲是等价的,但是下面一个比上面一个节约了5个字节。


使用gcc大概已经不能减少大小了,下面我们就使用linker--ld

[alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF
; tiny.asm
BITS 32
GLOBAL _start
SECTION .text
_start:
xor eax,eax
inc eax
mov bl,42
int 0x80

EOF
[alert7@redhat]# nasm -f elf tiny.asm
[alert7@redhat]# ld -s tiny.o
[alert7@redhat]# wc -c a.out
412 a.out

小了4个字节,应该是5个字节的,但是另外的一个字节被用来考虑对齐去了。

是否到达了极限了呢,能否更小?

hm.我们的程序代码现在只有7个字节长。是否ELF文件还有405字节的额外的负载呢 ?他们都是
些什么?

使用objdump来看看文件的内容:

[alert7@redhat]# objdump -x a.out | less
a.out: no symbols

a.out: file format elf32-i386
a.out
architecture: i386, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x08048080

Program Header:
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
filesz 0x00000087 memsz 0x00000087 flags r-x

Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000007 08048080 08048080 00000080 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .bss 00000001 08049087 08049087 00000087 2**0
CONTENTS
2 .comment 0000001c 00000000 00000000 00000088 2**0
CONTENTS, READONLY

[译者注:在我的机子上多了个.bss节,我想可能是跟ld版本有关。所以在我系统上
演示的一直比原作者上面的大:(
看来要想更小的话,还是可以考虑找个低版本的编译:)
]

如上,完整的.text节为7个字节大,刚好如我们刚才所说。

但是还有其他的节,例如".comment",谁安排它的呢?".comment"节大小为28个字节。
我们现在不知道.comment节到底是什么东西,但是可以大胆的说,它是不必须的。

.comment节在文件偏移量为00000087 (16进制)
我们来看看是什么东西

[alert7@redhat]# objdump -s a.out

a.out: file format elf32-i386

Contents of section .text:
8048080 31c040b3 2acd80 1.@.*..
Contents of section .bss:
8049087 00 .
Contents of section .comment:
0000 00546865 204e6574 77696465 20417373 .The Netwide Ass
0010 656d626c 65722030 2e393800 embler 0.98.

哦,是nasm自己的一段信息,或许我们应该使用gas.......

假如我们:

[alert7@redhat]# set -o noclobber && cat > tiny.s << EOF
.globl _start
.text
_start:
xorl %eax, %eax
incl %eax
movb $42, %bl
int $0x80
EOF

[alert7@redhat]# gcc -s -nostdlib tiny.S
[alert7@redhat]# ./a.out ; echo $?
42
[alert7@redhat]# wc -c a.out
368 a.out

[译者注:在作者机子上这里大小没有变化,但在我的系统上,这里变成了368
(跟作者的机子上一样了),比前面的所以的都要小
]


再用一下objdump,会有些不同:

Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000007 08048074 08048074 00000074 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000000 0804907c 0804907c 0000007c 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 0804907c 0804907c 0000007c 2**2
ALLOC

没有了commnet节,但是多了两个无用的节,用来存储不存在的数据。而且那些节居然还是0长度。
他们使文件大小变大。

所以它们都是没有用的,我们如何来去掉它们呢?

我们需要准备一些elf文件格式的知识。虽然我也已经翻译过《ELF文件格式》 ,
在http://www.xfocus.org/上可以找到,但是翻译的很垃圾,早已招人唾骂过了,
所以还是推荐大家看英文原版文档,而且是强烈推荐。



--------------------------------------------------------------------------------
elf文件格式英文文档下载地址:
ftp://tsx.mit.edu/pub/linux/packages/GCC/ELF.doc.tar.gz.
或者 http://www.muppetlabs.com/~breadbox/software/ELF.txt.

基本的,我们需要知道如下知识:

每一个elf文件都是以一个ELF header的结构开始的。该结构为52个字节长,并且包含了一个
信息部分,这些信息部分描述了文件的内容。例如,前16个字节包含了一个“标识符”,它
包含了ELF文件的魔术数,但字节的标记表明是32位的还是64位的,小端序还是大端序,等等。
在elf header包含的其他的信息还有,例如:目标体系;ELF文件是否是可执行的还是OBJECT
文件还是一个共享的库;程序的开始地址;program header table和section header table
在文件的偏移量。


两个表可以出先在文件的任何地方, 但是以前经常是直接跟在ELF HEADER后面,后来出现在
文件的末尾或许是靠近末尾。两个表有相试的功能,都是为了甄别文件的组成。但是,
section header table更关注的是识别在程序中不同部分在什么地方,然而,program
header table描述的是哪里和如何把那些部分转载到内存中。
简单的说,section header table 是被编译器(compiler)和连接器(linker)使用,program
header table是被程序转载器(loader)使用。对object 文件,program header talbe是
可选的,实际上从来也没有出现过。同样的,对于可执行文件来说,section header table
也是可选的,但是它却总是存在于可执行文件中。

因此,对于我们的程序来说,seciton header table是完全没有用的,那些sections也不会
影响到程序内存的映象。

那么,到底如何去掉它们呢?

我们必须自己来构造程序的ELF HEADER.
你也可以查看ELF文档和/usr/include/linux/elf.h得到相关信息,一个空的ELF可执行文件应该
象如下:


BITS 32

org 0x08048000

ehdr: ; Elf32_Ehdr
db 0x7F, "ELF", 1, 1, 1 ; e_ident
times 9 db 0
dw 2 ; e_type
dw 3 ; e_machine
dd 1 ; e_version
dd _start ; e_entry
dd phdr - $$ ; e_phoff
dd 0 ; e_shoff
dd 0 ; e_flags
dw ehdrsize ; e_ehsize
dw phdrsize ; e_phentsize
dw 1 ; e_phnum
dw 0 ; e_shentsize
dw 0 ; e_shnum
dw 0 ; e_shstrndx

ehdrsize equ $ - ehdr

phdr: ; Elf32_Phdr
dd 1 ; p_type
dd 0 ; p_offset
dd $$ ; p_vaddr
dd $$ ; p_paddr
dd filesize ; p_filesz
dd filesize ; p_memsz
dd 5 ; p_flags
dd 0x1000 ; p_align

phdrsize equ $ - phdr

_start:

; your program here

filesize equ $ - $$

该映象包含了一个ELF header ,没有section header table ,一个program header table 包含了
一个入口。该入口指示程序转载器把完整的文件装载到内存(一般的是包含自己的ELF header 和
program header table)开始地址为0x08048000(这是可执行文件装载的默认地址)的地方,并且
开始执行_start处代码,_start紧跟着program header table.没有.data段,没有.bss段
没有.comment段。

好了,现在我们的程序就变成这样了:

[alert7@redhat]# cat tiny.asm
; tiny.asm
org 0x08048000

ehdr: ; Elf32_Ehdr
db 0x7F, "ELF", 1, 1, 1 ; e_ident
times 9 db 0
dw 2 ; e_type
dw 3 ; e_machine
dd 1 ; e_version
dd _start ; e_entry
dd phdr - $$ ; e_phoff
dd 0 ; e_shoff
dd 0 ; e_flags
dw ehdrsize ; e_ehsize
dw phdrsize ; e_phentsize
dw 1 ; e_phnum
dw 0 ; e_shentsize
dw 0 ; e_shnum
dw 0 ; e_shstrndx

ehdrsize equ $ - ehdr

phdr: ; Elf32_Phdr
dd 1 ; p_type
dd 0 ; p_offset
dd $$ ; p_vaddr
dd $$ ; p_paddr
dd filesize ; p_filesz
dd filesize ; p_memsz
dd 5 ; p_flags
dd 0x1000 ; p_align

phdrsize equ $ - phdr
_start:
mov bl, 42
xor eax, eax
inc eax
int 0x80

filesize equ $ - $$


[alert7@redhat]# nasm -f bin -o a.out tiny.asm
[alert7@redhat]# chmod +x a.out
[alert7@redhat]# ./a.out ; echo $?
42


再看看大小:

[alert7@redhat]# wc -c a.out
93 a.out


真是奇迹,才93个字节大小了。

假如我们明白在可执行文件中的每个字节,我们或许还可以更小,也许很是极限了哦:)


--------------------------------------------------------------------------------

你可能已经注意到了:
1)ELF文件的不同部分允许被定位在任何地方(除了ELF header,它必须放在文件的开始),
并且它们可以交叠。
2)事实上一些字段到目前还没有被用到。

在鉴别文件字段最后有9个字节为0,我们的代码只有7个字节长,所以我们试图把代码放入
鉴别文件字段最后9个字节中,还有2个剩余。....


[alert7@redhat]# cat tiny.asm
; tiny.asm

BITS 32

org 0x08048000

ehdr: ; Elf32_Ehdr
db 0x7F, "ELF" ; e_ident
db 1, 1, 1, 0
_start: mov bl, 42
xor eax, eax
inc eax
int 0x80
db 0
dw 2 ; e_type
dw 3 ; e_machine
dd 1 ; e_version
dd _start ; e_entry
dd phdr - $$ ; e_phoff
dd 0 ; e_shoff
dd 0 ; e_flags
dw ehdrsize ; e_ehsize
dw phdrsize ; e_phentsize
dw 1 ; e_phnum
dw 0 ; e_shentsize
dw 0 ; e_shnum
dw 0 ; e_shstrndx

ehdrsize equ $ - ehdr

phdr: ; Elf32_Phdr
dd 1 ; p_type
dd 0 ; p_offset
dd $$ ; p_vaddr
dd $$ ; p_paddr
dd filesize ; p_filesz
dd filesize ; p_memsz
dd 5 ; p_flags
dd 0x1000 ; p_align

phdrsize equ $ - phdr

filesize equ $ - $$


[alert7@redhat]# nasm -f bin -o a.out tiny.asm
[alert7@redhat]# chmod +x a.out
[alert7@redhat]# ./a.out ; echo $?
42
[alert7@redhat]# wc -c a.out
84 a.out


现在我们的程序只有一个elf header和一个program header table入口,为了装载和运行程序,
这些是我们必要的。所以现在我们不能减少了!除非....


我们使elf header和program header table一部分重合或者说是交叠,有没有可能呢?

答案当然是有的,注意我们的程序,就会注意到在elf header最后8个字节和program header table
前8个字节是一样的,所以...

[alert7@redhat]# cat tiny.asm
; tiny.asm

BITS 32

org 0x08048000

ehdr:
db 0x7F, "ELF" ; e_ident
db 1, 1, 1, 0
_start: mov bl, 42
xor eax, eax
inc eax
int 0x80
db 0
dw 2 ; e_type
dw 3 ; e_machine
dd 1 ; e_version
dd _start ; e_entry
dd phdr - $$ ; e_phoff
dd 0 ; e_shoff
dd 0 ; e_flags
dw ehdrsize ; e_ehsize
dw phdrsize ; e_phentsize
phdr: dd 1 ; e_phnum ; p_type
; e_shentsize
dd 0 ; e_shnum ; p_offset
; e_shstrndx
ehdrsize equ $ - ehdr
dd $$ ; p_vaddr
dd $$ ; p_paddr
dd filesize ; p_filesz
dd filesize ; p_memsz
dd 5 ; p_flags
dd 0x1000 ; p_align
phdrsize equ $ - phdr

filesize equ $ - $$


[alert7@redhat]# nasm -f bin -o a.out tiny.asm
[alert7@redhat]# chmod +x a.out
[alert7@redhat]# ./a.out ; echo $?
42
[alert7@redhat]# wc -c a.out
76 a.out


现在已经不能够再更多的重叠那两个结构了,因为两个结构的字节没有再相同的了。

但是,我们可以再构造这两个结构,使它们有更多的相同部分。

到底linux会检查多少字段呢?例如,它会检查e_machine字段吗?

事实上很另人惊讶,一些字段居然被默默的忽略了。

因此:哪些东西才是ELF header中最重要的呢?最前的四个字节当然是的,它包含了一个
魔术数,否则linux不会继续处理它。在e_ident字段的其他3个字节不被检查,那就意味着
我们有不少于12个连续的字节我们可以设置为任意的值。e_type必须被设置为2(用来表明
是个可执行文件),e_machine必须为3。就象e_ident中的版本号一样,e_version被完全的
忽略。(这样做可以理解,因为现在只有一个版本的ELF标准)。e_entry当然要设置为正确
的值,因为它指向程序的开始。毫无疑问,e_phoff应该是program header table在文件中
的正确偏移量,e_phnum是program header table中所包含的正确的入口数。然而,e_flags
没有被当前的Intel体系使用,所以我们应该可以重新利用。e_ehsize用来校验elf header
所期望的大小,但是LINUX忽略了它。e_phentsize同样的确认program header table入口的
大小。但是只有在2.2.17以后的2.2系列内核中这个字段才是被检查的。早于2.2的和2.4.0的
内核是忽略它的。

program header table又是如何呢?
p_type必须是1(即PT_LOAD),表明这是个可载入的段。p_offset是开始装载的文件偏移量。
同样的,p_vaddr是正确的装载地址。注意:我们没有要求把它装载到0x08048000.
可用的地址为0-0x80000000,并且要页对齐。文档上说p_paddr被忽略,因此这个字段更是可
用的。p_filesz 指示了从文件中装载到内存中有多少字节,p_memsz指示了需要多大的内存段。
因此,他们的值应该是相关的。p_flags指示了给于内存段什么权限。可设置读,写,执行,
其他位也可能被设置,但是我们只需要最小权限。最后,p_align给出了对齐需求。该字段主要
使用在当重定位段包含了与位置无关的代码时,岂今为止,可执行文件将被LINUX忽略这个字段。

根据分析,我们从中可以看出一些必要的字段,一些无用的字段,这样,我们就可以重叠更多的
字数了。

[alert7@redhat]# cat tiny.asm
; tiny.asm◆

BITS 32

org 0x00200000

db 0x7F, "ELF" ; e_ident
db 1, 1, 1, 0
_start:
mov bl, 42
xor eax, eax
inc eax
int 0x80
db 0
dw 2 ; e_type
dw 3 ; e_machine
dd 1 ; e_version
dd _start ; e_entry
dd phdr - $$ ; e_phoff
phdr: dd 1 ; e_shoff ; p_type
dd 0 ; e_flags ; p_offset
dd $$ ; e_ehsize ; p_vaddr
; e_phentsize
dw 1 ; e_phnum ; p_paddr
dw 0 ; e_shentsize
dd filesize ; e_shnum ; p_filesz
; e_shstrndx
dd filesize ; p_memsz
dd 5 ; p_flags
dd 0x1000 ; p_align

filesize equ $ - $$


正如你看到的,program header table的前12个字节重叠在ELF header的最后12个字节里。
相当的吻合。ELF header重复中只有两部分会有麻烦。一是e_phnum字段,相对应的是p_paddr
是会被忽略。第二个是e_phentsize字段,它和p_vaddr前两个字节相一致,为了这个相一致,
使用了非标准的加载地址0x00200000,那么前面的两个字节就是0x0020.

[alert7@redhat]# nasm -f bin -o a.out tiny.asm
[alert7@redhat]# chmod +x a.out
[alert7@redhat]# ./a.out ; echo $?
42
[alert7@redhat]# wc -c a.out
64 a.out

well,现在大小为64字节了

如果我们使 program header table完全放在ELF header中,那么,呵呵,大小就可以更小了,
但是这样做行吗?

是的,是可能的。使program header table从第四个字节就开始,精心构造可执行的ELF文件。

我们注意到:
第一P_memsz指出了为内存段分配多少内存。明显的,它必须至少跟P_filesz一样大,
当然更大是没有关系的。

第二, 可执行位可以从p_flags字段中丢弃,linux会为我们设置它的。为什么这样会工作呢?
作者说不知道,又猜测了原因说是否因为入口指针指向了该段?

[★译者注:
但我知道,linux根本就没有为我们设置p_flags字段中的可执行位,可以工作,
只是因为Intel体系上根本就不具有执行保护功能,就是这个原因,才使得有人有
必要设计了类似堆栈不可运行的内核补丁程序。
]


[alert7@redhat]# cat tiny.asm
; tiny.asm

BITS 32

org 0x00001000

db 0x7F, "ELF" ; e_ident
dd 1 ; p_type
dd 0 ; p_offset
dd $$ ; p_vaddr
dw 2 ; e_type ; p_paddr
dw 3 ; e_machine
dd filesize ; e_version ; p_filesz
dd _start ; e_entry ; p_memsz
dd 4 ; e_phoff ; p_flags
_start:
mov bl, 42 ; e_shoff ; p_align
xor eax, eax
inc eax ; e_flags
int 0x80
db 0
dw 0x34 ; e_ehsize
dw 0x20 ; e_phentsize
dw 1 ; e_phnum
dw 0 ; e_shentsize
dw 0 ; e_shnum
dw 0 ; e_shstrndx

filesize equ $ - $$

p_flags字段从5变为4,这个4也是e_phoff字段的值,它给出了program header table在文件中
的偏移量。代码被放在从e_shoff 开始到e_flags内部结束。

注意到:装载地址被改变了更低了。只是为了保持e_entry的值到一个比较合适的小值,它刚好
也是P_mensz的数值。

[alert7@redhat]# nasm -f bin -o a.out tiny.asm
[alert7@redhat]# chmod +x a.out
[alert7@redhat]# ./a.out ; echo $?
42
[alert7@redhat]# wc -c a.out
52 a.out

现在,程序代码本身和program header table完全嵌入了ELF header,我们的可执行文件现在和
elf header一样大。而且可以正常运行。

最后,我们不禁还要问,是否到达了最小的极限呢?毕竟,我们需要一个完整的ELF header,否则
linux不会给我们运行的机会。

真的是这样吗 ?

错了,我们还可以运用最后一招卑鄙的哄骗技术了。

如果文件大小还没有整个ELF header大的话,linux还是会运行它的。并且把那些少的字节填充为
0。我们在文件的最后有不少于7个0,可以丢弃。


[alert7@redhat]# cat tiny.asm
; tiny.asm

BITS 32

org 0x00001000

db 0x7F, "ELF" ; e_ident
dd 1 ; p_type
dd 0 ; p_offset
dd $$ ; p_vaddr
dw 2 ; e_type ; p_paddr
dw 3 ; e_machine
dd filesize ; e_version ; p_filesz
dd _start ; e_entry ; p_memsz
dd 4 ; e_phoff ; p_flags
_start:
mov bl, 42 ; e_shoff ; p_align
xor eax, eax
inc eax ; e_flags
int 0x80
db 0
dw 0x34 ; e_ehsize
dw 0x20 ; e_phentsize
db 1 ; e_phnum
; e_shentsize
; e_shnum
; e_shstrndx

filesize equ $ - $$

[alert7@redhat]# nasm -f bin -o a.out tiny.asm
[alert7@redhat]# chmod +x a.out
[alert7@redhat]# ./a.out ; echo $?
42
[alert7@redhat]# wc -c a.out
45 a.out


讨论到此,一个elf可执行文件最小大小为45 bytes,我们被迫终止我们的讨论了。

--------------------------------------------------------------------------------
一个45字节大小的文件比一个用标准工具创建的最小可执行文件的1/8还要小,比用纯C代码
创建的1/50还要小。

这片文章中的一半ELF字段变量违反了标准的ELF规范,

以上程序中打上◆ 的程序,会使readelf core dump
[alert7@redhat]# readelf -a a.out
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 b3 2a 31 c0 40 cd 80 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 179
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x200008
Start of program headers: 32 (bytes into file)
Start of section headers: 1 (bytes into file)
Flags: 0x0
Size of this header: 0 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 1
Size of section headers: 0 (bytes)
Number of section headers: 64
Section header string table index: 0
readelf: Error: Unable to read in 0 bytes of section headers

Program Header:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x00200000 0x00000001 0x00040 0x00040 R E 0x1000

There is no dynamic segment in this file.
Segmentation fault (core dumped)

呵呵,居然出现了可爱的core dumped

[alert7@redhat]# ls -l /usr/bin/readelf
-rwxr-xr-x 1 root root 132368 Feb 5 2000 /usr/bin/readelf

:(不是带s位的,也就懒的去看它到底哪里出问题了。

创建的这种超小的elf文件的确比较畸形,连objdump都不能dump它们了。
[alert7@redhat]# objdump -a a.out
objdump: a.out: File format not recognized
ky640 2001-11-23
  • 打赏
  • 举报
回复
这问题真无聊,写程序最主要的是易于交流和合作,其次是运行效率(有时例外),程序大小用得着考虑吗?
caigzhi 2001-11-23
  • 打赏
  • 举报
回复
直接写二进制PE格式执行文件
PM 2001-11-23
  • 打赏
  • 举报
回复
请把你的BUFFER开小点吧。并且多用函数。
tigerhnit 2001-11-23
  • 打赏
  • 举报
回复
我用vc编写的程序就1K,不过什么也没有干,你用VC默认选向编写一个什么也不作的程序就很大,主要其中好像叫什么afc的东西作怪.
Netguy 2001-11-22
  • 打赏
  • 举报
回复
用Win32 ASM写程序,一般编译出来是8K,还可以压缩一下。
sweet 2001-11-22
  • 打赏
  • 举报
回复
实现同一种功能,汇编程序是最小的,工作量是最大的。
XiangDong 2001-11-22
  • 打赏
  • 举报
回复
用汇编把
atht 2001-11-22
  • 打赏
  • 举报
回复
程序编译后,用“吸脂”工具减肥。因为PE文件是按节对齐的,这样方便映射到内存中,一节就是4K,这样编译后必然程序中有很多空隙。用“吸脂”工具,能除掉这些空闲,文件会小些。
dog_dog 2001-11-22
  • 打赏
  • 举报
回复
study
rola 2001-11-22
  • 打赏
  • 举报
回复
这样的要求,是不是要做几个虫虫给大家,是不是太黑了
加载更多回复(32)
让您爱不释手的专业串口调试软件,使用完全免费! 本软件可以在Win95/98、Win2000、WinNT、WinXP下面运行.软件功能主要为: 1.接收从串口进来的数据并在窗口显示. 2.所接收到的数据数据显示方式可以选择为字符方式或者HEX方式 4.中文显示无乱码,且不影响速度 5.串口波特率可以选择为110bps-256000bps.(波特率>115200时需要硬件支持) 6.可以即时显示存在的串口号.如果您增加了usb转串口等设备,串口号也会在列表中出现. 7.可以选择“5、6、7、8”四种数据长度. 8.可以选择为“1、1.5、2”三种停止位.(1.5停止位需要硬件支持) 8.第9位数据可以选择为“无、奇校验、偶校验、1、0”四种方式. 9.可以选择“无流控、软流控、硬流控、自定义”四种流控方式. 10.串口设置和字符串操作等设置在程序关闭时自动保存,打开时自动载入. 11.可以在接收窗口按键即发送该键值. 12.可以在字符串输入框输入您发送的字符串,并发送. 13.可以在字符串输入框输入您发送的HEX数据串,数据的值从00到FF,没有任何限制. 14.可以定时重复发送数据,并可以设置发送时间间隔. 15.可以在发送字符串时选择发送新行,即自动加上回车换行. 16.可以显示当前串口的CTS、DSR、RLSL(CD)信号线的状态. 17.可以自由控制当前串口的DTR、RTS信号线的输出状态. 18.可以打开一个文本文件或者一个二进制文件预览其内容,查看方式可以是文本或者HEX方式. 19.可以打开一个文本文件或者一个二进制文件并以当前波特率发送到串口. 20.可以保存窗口内容到一个文本文件,文件名取自当前时间,保存在当前目录. 21.可以即时显示发送的字节数和接收到的字节数,按清除窗口将会清零. 22.带有功能强大的扩展功能:多条字符串发送定义和网上查找串口资料等. 23.可以定义最多32条预备发送的字符串,每条字符串可以定义为HEX数据串或者字符串方式.在每一条数据的左边打勾就表示这是一条hex数据串. 24.点击字符串右边的标号即可以发送这条定义好的字符串. 25.可以设置为循环发送你定义过的多条字符串,并且可以设置发送时间间隔. 26.在串口资料栏您可以从mcu51网站或者Google查找有关串口技术资料. 27.在串口资料栏您可以进入技术讨论bbs,在此发表您的高见或者提出您的问题和需求. 28.在产品信息栏您可以获得现时最新的产品信息. 29.这是个绿色软件,单个文件即可执行,不会给您的机器增加任何负担. 此版本使用C++Builder编写,相对于上一版本SSCOM2.0,主要改进在: 1.程序更稳定可靠,修改了一些报错信息.使用更加人性化。 2.修改了避免显示汉字乱码的算法,快了许多. 3.hex数据输入的错误兼容性. 4.发送字符串可以加发回车换行. 5.可以保存窗口内容到文件. 6.发送和接收的字符数统计更准确. 7.不再接收到一定数量字符数就清屏,因为发现即使收到很多内容也不会溢出,速度仍然很快. 8,可以打开二进制文件并发送,从前只能发文本文件. 9.打开文件后可以用asc方式或者hex方式预览文件中前4K内容. 10.发送文件前告诉操作者需要发送多少时间.免得久等. 11.新增功能强大的扩展功能,多达32条自定义字符串操作,程序关闭时这些字符串会自动保存,下次开机时再载入,每条可以定义为HEX数据串或者ASC字符串,按后边的数字按钮可以发送.也可以自动循环发送定义过的字符串. 12.新增串口设置自动保存. 13.加入了网络支持功能,用户很方便讨论问题和找到技术支持. 由51单片机世界提供

16,472

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧