文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>牛X的Hello World!

牛X的Hello World!

时间:2007-05-20  来源:souldemo

我在论坛里问过如何写机器码。我希望能在linux下找个类似于windows中DEBUG的东西
而不是像我这样自己打文件头。可是被人耻笑说我脸计算机原理都不懂,我真的很伤心。
我一直信奉:The limit, forever is remains for these ignorant people。
(限制,永远是留给那些无知的人的。)
我承认他懂的比我多,但是他不该这么说我,这里我还是要重申,我知道linux不是windows,
但是没有DEBUG,我还是能写出来,就是麻烦点。

想编写可执行程序,就得了解ELF。
在研究下ELF文件头结构之前先看下32位平台的几个数据类型:

/* 32-bit ELF base types. */
typedef __u32    Elf32_Addr;
typedef __u16    Elf32_Half;
typedef __u32    Elf32_Off;
typedef __s32    Elf32_Sword;
typedef __u32    Elf32_Word;

#define EI_NIDENT    16

typedef struct elf32_hdr{

    /** 魔数和一些平台版本信息。 前四个字节必须是0x454c46,
    [4]表示平台位数, 1表示32位, 2表示64位。
    [5]表示数据编码格式,1 表示小尾,2表示大尾。
    [6]指定ELF头部版本,目前使用1,
    这三位的完整取值可见/usr/include/linux/elf.h
    中的定义。

    [7]-[16]为填充,为0。
    **/

  unsigned char    e_ident[EI_NIDENT];    /**魔数相关信息 **/
  Elf32_Half    e_type;    /** 文件类型 **/
  Elf32_Half    e_machine; /** 硬件平台 **/
  Elf32_Word    e_version; /** 文件版本 **/
  Elf32_Addr    e_entry;  /** 程序入口点  **/
  Elf32_Off    e_phoff;    /** 程序头表偏移量 **/
  Elf32_Off    e_shoff;    /** 节头表偏移量 **/
  Elf32_Word    e_flags;    /** 处理器特定标志 **/
  Elf32_Half    e_ehsize;    /** ELF头长度,即此结构长度, 52 **/
  Elf32_Half    e_phentsize;    /** 程序头表中一个条目长度 **/
  Elf32_Half    e_phnum;    /** 程序头表条目个数 **/
  Elf32_Half    e_shentsize;    /** 节头表中一个条目长度 **/
  Elf32_Half    e_shnum;    /** 节头表条目个数 **/
  Elf32_Half    e_shstrndx;    /** 节头表字符串表索引 **/
} Elf32_Ehdr;

可以通过readelf -h 程序明查看可执行程序的ELF头,下面是个典型输出:

[souldump@localhost bin]$ readelf -h cpuid
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048074
  Start of program headers:          52 (bytes into file)
  Start of section headers:          252 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         2
  Size of section headers:           40 (bytes)
  Number of section headers:         6
  Section header string table index: 3

ELF头结构之后跟随的是个程序头数组,从程序执行的角度分析,ELF文件被分为很多的段。
分别保存数据,指令,已初始化的数据。

typedef struct elf32_phdr{
  Elf32_Word    p_type;    /** 段类型 **/
  Elf32_Off    p_offset;    /** 段位置相对于文件开始的位移 **/
  Elf32_Addr    p_vaddr;    /** 段在虚拟内存中的地址 **/
  Elf32_Addr    p_paddr;    /** 段在物理内存中的地址 **/
  Elf32_Word    p_filesz;    /** 段在文件中长度 **/
  Elf32_Word    p_memsz;    /** 段在内存中长度 **/
  Elf32_Word    p_flags;    /** 段标志 **/
  Elf32_Word    p_align;    /** 段在内存中对齐标志 **/
} Elf32_Phdr;

查看cpuid程序的输出:

[souldump@localhost bin]$ readelf -l cpuid

Elf file type is EXEC (Executable file)
Entry point 0x8048074
There are 2 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0x000ab 0x000ab R E 0x1000
  LOAD           0x0000ac 0x080490ac 0x080490ac 0x00029 0x00029 RW  0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .text
   01     .data

由于这是个由汇编语言编写的代码,没有调用GLIBC的库函数,因此它不需要解释器,不用加载
/lib/ld-linux.so.2.

下面是一个由C编译的程序的部分输出,如果想了解更详细的内容可以自己查看:
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
  INTERP         0x000114 0x08048114 0x08048114 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x00464 0x00464 R E 0x1000
  LOAD           0x000464 0x08049464 0x08049464 0x00100 0x00104 RW  0x1000
  DYNAMIC        0x000478 0x08049478 0x08049478 0x000c8 0x000c8 RW  0x4
  NOTE           0x000128 0x08048128 0x08048128 0x00020 0x00020 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

程序头后面跟随的是节头数组:

typedef struct {
  Elf32_Word    sh_name;    /** 小节名,在字符串表中索引 **/
  Elf32_Word    sh_type;    /** 小节类型 **/
  Elf32_Word    sh_flags;    /** 小节属性 **/
  Elf32_Addr    sh_addr;    /** 小节在运行中的虚拟地址 **/
  Elf32_Off    sh_offset;    /** 小节的文件偏移 **/
  Elf32_Word    sh_size;    /** 小节的大小,字节为单位 **/
  Elf32_Word    sh_link;    /** 链接另外一小节的索引 **/
  Elf32_Word    sh_info;    /** 附加的小节信息 **/
  Elf32_Word    sh_addralign;    /** 小节的对齐 **/
    /** 如果一个小节保存着一张入定大小入口的表,就是入口的大小 否则为0 **/
  Elf32_Word    sh_entsize;
} Elf32_Shdr;

通常的C程序如果没使用其他共享库,会有27个section header.如果使用了其他的共享库,
则会按每个共享库一个条目(连接器ld-linux.so.2不算)。
比如/usr/bin/lsattr,由于使用了

  0: libe2p.so.2          2007-03-22T05:13:36 0xda8e15c5 0       0      
  1: libcom_err.so.2      2007-03-22T05:12:10 0x805a324f 0       0      
  2: libc.so.6            2007-03-22T05:12:01 0x9e4b904b 0       0      
  3: /lib/ld-linux.so.2   2007-03-22T05:12:01 0x86c26626 0       0      

因此会增加3个条目。
根据我对汇编语言编写的可执行代码分析,于C的方式很有很大区别。
使用汇编编译的程序,data节的数据,字符串末尾并不会添加"\0",输出到哪要靠用户自己控制,
而C编译的程序则会在字符串间添加.用C的程序data节的数据是按4字节对齐,但是,汇编中字符
串和数据是挨着排放的,后面直接安放了shstrtab节的数据。程序中定义的变量在函数入口开始
之前排放,就是在.text段开始处分配,而函数的入口则在占用的空间之后。比如.text段加载地址
 000000,而我们定义了两个int型的变量,那么函数的实际入口应该为000008,(32位平台int为4字节
同样,如果增加一个字符串,那么也会增加一个指针的偏移(相关节,头的偏移和值也得修改)
你可以很简单的通过readelf程序的输出值比较得到结果。

从程序执行的角度来看,section头不是必须的,而从编译角度来看,程序头也不是必须的。
为了简单起见。我就没要section头,而且为了扩展方便,修改了代码段与数据段的位置。

这是我写出来的Hello World!程序,(虽然确很简单,但还是能说明问题):
用hexedit 16进制文本编辑器,编辑的。
最核心的应该算是那几条指令编码:

eax 的寄存器选择位为0, ebx的为3, 根据mov指令中的:
B8+ rd     MOV r32, imm32   Move imm32 to r32.
以下两句:
        movl $4, %eax
        movl $1, %ebx
汇编为:
    B8 04 00 00 00
    BB 01 00 00 00
ECX的寄存器选择位为1.edx为2,第一句使用立即数寻址,虽然这是个变量,但是他存储的是地址。
从程序执行的角度来看,他存储的是字符串的的基地址。
所以下面两句:
        movl $output, %ecx     //随便取的名字。
        movl $13, %edx
汇编为:
    B9 XX XX XX XX (这里是字符串的首地址)
    BA 0D 00 00 00

这是int指令的描述:
CD ib     INT imm8           Interrupt vector number specified by                                        immediate byte.
这句
        int $0x80
被汇编为:
    CD 80 (这里是一个字节啊,不是4个!)

        movl $1, %eax
        movl $0, %ebx
        int $0x80

很容易写出还是上面的:

    B8 01 00 00 00
    BB 00 00 00 00
    CD 80 00 00 //最后这俩是填充的。
    
/现在,这个.text节应该完成了:
当然了,可以直接写汇编的,然后编译后,然后在用hexedit编辑,把
指令拷贝出来,然后再修改地址。

7F 45 4C 46  01 01 01 00  00 00 00 00  00 00 00 00
02 00 03 00  01 00 00 00  84 80 04 08  34 00 00 00
74 00 00 00  00 00 00 00  34 00 20 00  02 00 00 00
00 00 00 00  01 00 00 00  00 00 00 00  00 80 04 08
00 80 04 08  A5 00 00 00  A5 00 00 00  05 00 00 00
00 01 00 00  01 00 00 00  74 00 00 00  74 90 04 08
74 90 04 08  0D 00 00 00  0D 00 00 00  06 00 00 00
00 01 00 00  48 65 6C 6C  6F 20 57 6F  72 6C 64 21
0A 00 00 00  B8 04 00 00  00 BB 01 00  00 00 B9 74
90 04 08 BA  0C 00 00 00  CD 80 B8 01  00 00 00 BB
00 00 00 00  CD 80 00 00

这个是我从上面的代码中分离的模板,认为可以做个程序通用部分,(代码可数据部分自己去写)
 注:前面的是文件偏移量。

00000000   7F 45 4C 46  01 01 01 00  00 00 00 00  00 00 00 00
00000010   02 00 03 00  01 00 00 00  HH HH HH HH  34 00 00 00
00000020   74 00 00 00  00 00 00 00  34 00 20 00  02 00 00 00
00000030   00 00 00 00
            01 00 00 00  00 00 00 00  00 80 04 08
00000040   00 80 04 08  II II II II  II II II II  05 00 00 00
00000050   00 01 00 00 
            01 00 00 00  JJ JJ JJ JJ  KK 90 04 08
00000060   KK 90 04 08  LL LL LL LL  LL LL LL LL  06 00 00 00
00000070   00 01 00 00
            48 65 6C 6C  6F 20 57 6F  72 6C 64 21
00000080   0A 00 00 00
            B8 04 00 00  00 BB 01 00  00 00 B9 MM
00000090   MM MM MM BA  0C 00 00 00  CD 80 B8 01  00 00 00 BB
000000A0   00 00 00 00  CD 80 00 00

需要修改的部分已经标记,可参考结构解释填写。
HH HH HH HH: 程序入口地址
II II II II 指令在文件中长度,第二个为内存中的长度,二者相等。
JJ JJ JJ JJ 数据在文件中的偏移。
KK: 加载地址,应该说连这4个字节都算在内,不过需要写上很长的数据
    和代码,
LL LL LL LL:数据在文件和内存中的长度,

MM MM MM MM:这个地址是我用的字符串在加载到内存后的地址,很容易通过
程序头的首地址确定。

这个简单的程序什么也做不了,我一直希望能够写机器码,以后有了这个相信容易多了。
而且这个程序,比用汇编编写的还要小很多。

后记:

正如我在论坛中所说的,代码还可以简单到只有一个代码段,而把数据嵌套到代码段里面。
不管怎么说,这个争论到此算是结束了。

参考资料:《Executable and Linkable Format (ELF)》(还有网上中文版的翻译)
    《Intel® 64 and IA-32 Architectures Software Developer’s Manual》

 
相关阅读 更多 +
排行榜 更多 +
rento大富翁手游

rento大富翁手游

休闲益智 下载
冲撞赛车3无限金币版

冲撞赛车3无限金币版

赛车竞速 下载
未来教育考试

未来教育考试

学习教育 下载