Tuesday, March 2, 2010

Hello World - 更加精小的 code size

這裡所知道,要產生更小的Hello World程式,就需要用到純組合語言來完成,其純組合語言的優點...
  • 可以精確的控式Program Counter(PC)的變化(C語言內嵌組合語言雖可以做到,但程式碼需存在於function之中,也就是說在編譯C語言中,編譯器會做stack處理)
  • 可以精確的處理section的內容
  • 可以加入機械語言
    所以使用組合語言可以使code size做的更小 (當然一般來說,一個有經驗的programmer使用組合語言編寫程式會比使用C語言編寫程式所產生的code size還小的原因,這裡所描述的理由只是眾多原因之一)

    1 /*
    2 * hello2.c
    3 */
    4 unsigned char *str= "Hello World\n";
    5
    6 int _start(void)
    7 {
    8 /* write(STDOUT_FILENO, str, sizeof(str)); */
    9 asm("movl $14, %%edx;\
    10 movl %0, %%ecx;\
    11 movl $1, %%ebx;\
    12 movl $4, %%eax;\
    13 int $0x80;"
    14 : :"m"(str));
    15
    16 /* exit(0); */
    17 asm("xorl %ebx, %ebx;\
    18 movl $1, %eax;\
    19 int $0x80;");
    20 }

    第4行字串的安排及第6行_start() function,轉或成組合語言結果...

    1 /* as helloworld.s -o helloworld.o; ld helloworld.o -o helloworld */
    2 /* gcc helloworld.s -nostartfiles -nostdlib -static -o hellworld */
    3
    4 .text
    5 .globl _start
    6
    7 _start:
    8 mov $13, %edx
    9 mov $msg, %ecx
    10 mov $1, %ebx
    11 mov $4, %eax
    12 int $0x80
    13
    14 movl $0, %ebx /* exit code */
    15 movl $1, %eax /* exit function */
    16 int $0x80
    17
    18 .data
    19 msg:
    20 .string "Hello world\n"

    其編譯方式有兩種(於1、2行註解中),此程式中的進入點直接執行第8行程式,且只有安排一個data section(.data)。
    其編譯、執行結果...

    $ gcc helloworld.s -nostartfiles -nostdlib -static -o hellworld
    $ strip hellworld
    $ objdump -h hellworld

    hellworld: file format elf32-i386

    Sections:
    Idx Name Size VMA LMA File off Algn
    0 .text 00000022 08048074 08048074 00000074 2**2
    CONTENTS, ALLOC, LOAD, READONLY, CODE
    1 .data 0000000d 08049098 08049098 00000098 2**2
    CONTENTS, ALLOC, LOAD, DATA
    $
    $ ls -l hellworld
    -rwxr-xr-x 1 lungswu lungswu 348 2010-03-01 11:40 hellworld
    $
    $ ./hellworld
    Hello world
    $
    $

    所以產生348 bytes 大小的執行檔,組合語言中因為沒有定義.rodata section,所以編譯連結出來的執行檔也無.rodata section。



    本文開頭第2點有說到....組合語言可以"可以精確的處理section的內容"...
    先來將hellworld執行檔反組譯...

    $ objdump -S helloworld

    helloworld: file format elf32-i386


    Disassembly of section .text:

    08048074 <.text>:
    8048074: ba 0d 00 00 00 mov $0xd,%edx
    8048079: b9 98 90 04 08 mov $0x8049098,%ecx
    804807e: bb 01 00 00 00 mov $0x1,%ebx
    8048083: b8 04 00 00 00 mov $0x4,%eax
    8048088: cd 80 int $0x80
    804808a: bb 00 00 00 00 mov $0x0,%ebx
    804808f: b8 01 00 00 00 mov $0x1,%eax
    8048094: cd 80 int $0x80


    在上述反組譯的內容中,其執行碼剛好跟helloworld.s的組合語言做一對一的對應。
    1. 其中程式進入點為0x8048074。
    2. 於0x8048079位址,是將0x8049098的十六進制值填入站存器ECX中。值0x8049098剛好是symbol msg的位址。
    3. 程式執行到0x8048094便結束。
    所以...於位址 0x8048094 + 2 = 0x8048096 可以自行加入所需要額外的資料也不會對整個執行造成不良的影響 ....

    1 /* as helloworld2.s -o helloworld2.o; ld helloworld.o -o helloworld2 */
    2 /* gcc helloworld2.s -nostartfiles -nostdlib -static -o helloworld2 */
    3
    4 .text
    5 .globl _start
    6
    7 _start:
    8 mov $13, %edx
    9 mov $msg, %ecx
    10 mov $1, %ebx
    11 mov $4, %eax
    12 int $0x80
    13
    14 movl $0, %ebx /* exit code */
    15 movl $1, %eax /* exit function */
    16 int $0x80
    17
    18 msg:
    19 .string "Hello world\n"

    所以...這樣可以將"Hello World\n"的字串內容接到int 0x80之後,也將.data的section移除...

    $ gcc helloworld2.s -nostartfiles -nostdlib -static -o hellworld2
    $ strip helloworld2
    $ objdump -h helloworld2

    helloworld2: file format elf32-i386

    Sections:
    Idx Name Size VMA LMA File off Algn
    0 .text 0000002f 08048054 08048054 00000054 2**2
    CONTENTS, ALLOC, LOAD, READONLY, CODE
    $ ./helloworld2
    Hello world
    $
    $ ls -l helloworld2
    -rwxr-xr-x 1 lungswu lungswu 268 2010-03-01 13:33 hellworld2
    $
    這樣的結果...
    .data section已經移除,整個執行檔剩下.text section,大小變為 268 bytes。



    附註...組合語言中加入"機械語言"...

    因為組合語言中可以自行將入不同二進制的資料,如以下反組譯的結果...

    $ objdump -S helloworld2

    helloworld2: file format elf32-i386


    Disassembly of section .text:

    08048054 <.text>:
    8048054: ba 0d 00 00 00 mov $0xd,%edx
    8048059: b9 76 80 04 08 mov $0x8048076,%ecx
    804805e: bb 01 00 00 00 mov $0x1,%ebx
    8048063: b8 04 00 00 00 mov $0x4,%eax
    8048068: cd 80 int $0x80
    804806a: bb 00 00 00 00 mov $0x0,%ebx
    804806f: b8 01 00 00 00 mov $0x1,%eax
    8048074: cd 80 int $0x80
    8048076: 48 dec %eax
    8048077: 65 gs
    8048078: 6c insb (%dx),%es:(%edi)
    8048079: 6c insb (%dx),%es:(%edi)
    804807a: 6f outsl %ds:(%esi),(%dx)
    804807b: 20 77 6f and %dh,0x6f(%edi)
    804807e: 72 6c jb 0x80480ec
    8048080: 64 0a 00 or %fs:(%eax),%al
    $

    其實上面反組譯後位址 0x8048076 到 0x8048080 + 3 的地方是symbol msg的資料內容,因為放置於.text中,objdump或者program loader會將這新資料當作程式碼放置。

    所以,helloworld中要插入機械語言(比方說int 0x80的機械碼為0xCD 0x80,則可以這樣編寫(注意下面程式16行)...

    1 /* as helloworld3.s -o helloworld3.o; ld helloworld3.o -o helloworld3 */
    2 /* gcc helloworld3.s -nostartfiles -nostdlib -static -o helloworld3 */
    3
    4 .text
    5 .globl _start
    6
    7 _start:
    8 mov $13, %edx
    9 mov $msg, %ecx
    10 mov $1, %ebx
    11 mov $4, %eax
    12 int $0x80
    13
    14 movl $0, %ebx /* exit code */
    15 movl $1, %eax /* exit function */
    16 .byte 0xCD, 0x80 /* Machine code int 0x80 */
    17
    18 .data
    19 msg:
    20 .string "Hello world\n"

    結果如下...

    $ gcc helloworld3.s -nostartfiles -nostdlib -static -o helloworld3
    $ objdump -S helloworld3

    helloworld3: file format elf32-i386


    Disassembly of section .text:

    08048074 <_start>:
    8048074: ba 0d 00 00 00 mov $0xd,%edx
    8048079: b9 98 90 04 08 mov $0x8049098,%ecx
    804807e: bb 01 00 00 00 mov $0x1,%ebx
    8048083: b8 04 00 00 00 mov $0x4,%eax
    8048088: cd 80 int $0x80
    804808a: bb 00 00 00 00 mov $0x0,%ebx
    804808f: b8 01 00 00 00 mov $0x1,%eax
    8048094: cd 80 int $0x80
    $ ./helloworld3
    $
    Hello world
    $
    $
  • No comments:

    Post a Comment