使用GCC生成無格式二進制文件(plain binary files)

使用C語言生成一個二進制文件

使用自己喜歡的文本編輯器寫一個test.c:

int main()

{

}

再使用如下命令編譯:

gcc –c test.c

ld –o test –Ttext 0x0 –e main test.o

objcopy –R .note –R .comment –S –O binary test test.bin

最后生成的二進制文件是test.bin,可以使用你喜歡的反匯編工具看看這個文件里到底是什么。我使用Linux下的objdump進行反匯編:

objdump –D –b binary –a i386 test.bin

結果如下:

00000000 <main>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????c9????????????????????????leave?

??11:??????????c3????????????????????????ret??

其中第一列是指令的內存地址;第二列是指令的機器碼;第三列是匯編指令。相信你的結果與此同。如果你的gcc與我的不一樣,例如2.7.x版本的gcc,你的結果很可能會有所不同,缺少如下的四條指令,這是正常的,這兩個版本的gcc所使用的堆棧框架不同(下面介紹的例子也會因為編譯器版本的不同造成其結果有別):

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp #堆棧對齊,以16Bytes為單位分配局部變量空間

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

上述代碼都是32-bit代碼,你需要在像Linux這樣的?32-bit環境下運行,并且是保護模式。也可以只用下面的指令直接生成test.bin:

gcc –c test.c

ld –Ttext 0x0 –e main --oformat binary –o test.bin test.o

上面的test.c中只有一個函數,而且還只是個框架。其反匯編代碼也沒什么難理解的。

3.?????????編寫帶局部變量的程序

再創建一個新的test.c,看看gcc是如何處理局部變量的。

int main()

{

int i;

i=0x12345678;

}

使用上述兩種方法的人一種編譯,生成test.bin。然后使用objdump進行反匯編:

00000000 <main>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????c7 45 fc 78 56 34 12????movl???$0x12345678,0xfffffffc(%ebp)

??17:??????????c9????????????????????????leave?

??18:??????????c3????????????????????????ret

與第一個例子相比,開頭的六條指令和最后的兩條指令完全相同,僅有一條指令不同。這條語句是給局部變量賦值,其空間的分配在前面已經進行了。在gcc中,堆棧中的局部變量空間按16字節為單位進行分配,而不是通常的1字節為單位。如果將

int i;

i=0x12345678;

改為

int i=0x12345678;

其結果沒有區別。但是,如果是全局變量,就不一樣了。

4.?????????編寫帶全局變量的程序

將test.c改為:

int i;

int main()

{

i=0x12345678;

}

使用同樣的方法編譯,然后再進行反匯編:

00000000 <main>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????c7 05 1c 10 00 00 78???movl???$0x12345678,0x101c

??17:??????????56 34 12

??1a:?c9????????????????????????leave?

??1b:??????????c3????????????????????????ret???

我們定義的全局變量被放到了0x101c處,這是gcc默認以page-align對齊數據段的結果,此處的page與頁式內存管理中的page沒有關系。在使用ld鏈接時,使用-N參數可以關閉對齊效果。

00000000 <main>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????c7 05 1c 00 00 00 78???movl???$0x12345678,0x1c

??17:??????????56 34 12

??1a:?c9????????????????????????leave?

??1b:??????????c3????????????????????????ret?

正如我們看到的,數據段緊接著代碼段。我們也可以明確的指定數據段的位置,試試下面的命令再進行編譯:

gcc –c test.c

ld –Ttext 0x0 –Tdata 0x1234 –e main –N --oformat binary –o test.bin test.o

然后再使用objdump進行反匯編:

00000000 <.data>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????c7 05 34 12 00 00 78???movl???$0x12345678,0x1234

??17:??????????56 34 12

??1a:?c9????????????????????????leave?

??1b:??????????c3????????????????????????ret???

現在,我們定義的全局變量被放到0x1234處了。通過給ld指定-Tdata參數,可以自由的定義數據段的地址,如果不指定,數據段在代碼段后。

再看看直接給全局變量進行初始化的情況。

const int I=0x12345678;

int main()

{

}

仍然使用上面的方法進行編譯、鏈接、反匯編,其結果如下:

00000000 <.data>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????c9????????????????????????leave?

??11:??????????c3????????????????????????ret???

??12:??????????00 00??????????????????????add????%al,(%eax)

??14:??????????78 56??????????????????????js?????0x6c

??16:??????????34 12??????????????????????xor????$0x12,%al

代碼以4Bytes對齊,全局變量被直接存儲在代碼段之后的數據段,ld直接將常數放到了全局變量的位置,一步到位。

使用如下命令可以看到更多細節:

objdump –D test.o

可以看到如下的結果:

test.o:?????file format elf32-i386

?

Disassembly of section .text:

?

00000000 <main>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????c9????????????????????????leave?

??11:??????????c3????????????????????????ret???

Disassembly of section .data:

Disassembly of section .rodata:

?

00000000 <i>:

???0:??????????78 56??????????????????????js?????58 <main+0x58>

???2:??????????34 12??????????????????????xor????$0x12,%al

我們可以更清楚地看到,在.c文件中定義的全局常量被放在了只讀的數據段中了。再看下面的一段代碼:

int I=0x12345678;

const int c=0x12345678;

int main()

{

}

還是使用上面的方法編譯、鏈接、反匯編,可以到到如下結果:

test.o:?????file format elf32-i386

?

Disassembly of section .text:

?

00000000 <main>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????c9????????????????????????leave?

??11:??????????c3????????????????????????ret???

Disassembly of section .data:

?

00000000 <i>:

???0:??????????78 56??????????????????????js?????58 <main+0x58>

???2:??????????34 12??????????????????????xor????$0x12,%al

Disassembly of section .rodata:

?

00000000 <c>:

???0:??????????78 56??????????????????????js?????58 <main+0x58>

???2:??????????34 12??????????????????????xor????$0x12,%al

可以看出,整數I被放在了普通的數據段中,常數c被放在了只讀數據段中了。當使用全局變量(常量)時,ld會自動的使用合適的數據段存儲他們。

5.?????????處理指針

使用如下代碼來查看gcc處理指針變量的情況:

int main()

{

int I;

int* p;

p=&I;

*p=0x12345678;

}

使用objdump查看生成的機器代碼:

00000000 <main>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:?????????8d 45 fc?????????????????????lea????0xfffffffc(%ebp),%eax

??13:?????????89 45 f8????????????????????mov????%eax,0xfffffff8(%ebp)

??16:?????????8b 45 f8????????????????????mov????0xfffffff8(%ebp),%eax

??19:?????????c7 00 78 56 34 12????movl???$0x12345678,(%eax)

??1f:?c9????????????????????????leave?

??20:??????????c3????????????????????????ret???

一開始,gcc已經為局部變量預分配了至少8Bytes的空間,并且使esp以16Bytes邊界對齊,如果還需要額外的空間,gcc將按照16Bytes為單位進行分配,而不是其他編譯器所使用的以1Byte為單位進行分配。變量I位于ebp-4,變量p位于ebp-8,lea指令將I的有效地址放入eax中,然后又被放入p中。最后,將0x12345678賦給p指向的變量I。

6.?????????關于函數調用

看如下代碼:

void func();

int main()

{

func();

}

void func()

{

}

再看生成的二進制代碼:

00000000 <.data>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????e8 03 00 00 00????????????????call???0x18

??15:??????????c9????????????????????????leave?

??16:??????????c3????????????????????????ret???

??17:??????????90????????????????????????nop???

??18:??????????55????????????????????????push???%ebp

??19:??????????89 e5??????????????????????mov????%esp,%ebp

??1b:??????????c9????????????????????????leave?

??1c:?c3????????????????????????ret???

主函數main通過call指令調用了空函數func,該函數與main大同小異。為ld指定-Map開關輸出map文件,可以得到更詳細的信息。

.text???????????0x00000000???????0x1d

?*(.text .stub .text.* .gnu.linkonce.t.*)

?.text??????????0x00000000???????0x1d???????test.o

????????????????0x00000000????????????????main

????????????????0x00000018????????????????func

第一列是段名,這里是.text;第二列是起始位置,第三列是段長度,最后一列是附加信息,如函數名、所出自的目標文件等。可以看到,.text段從0x0開始,長度為0x1d;函數func從0x18開始。

7.?????????函數的返回值

看下面的代碼,主函數main返回一個整型值:

int main()

{

return 0x12345678;

}

所生成的二進制代碼與其他編譯器大同小異:

00000000 <main>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????b8 78 56 34 12????????????????mov????$0x12345678,%eax

??15:??????????c9????????????????????????leave?

??16:??????????c3????????????????????????ret???

你已經看到了,gcc使用eax傳遞返回值。因為返回值就是eax寄存器的值,所以你可以隱含的返回,甚至什么都不返回。因為返回值保存在寄存器中,進行函數調用時,經常忽略返回值。例如,我們經常這樣調用函數:

printf(…);

該函數是有返回值的。如果函數返回的數據大于4Bytes,就不能再使用這種方法返回數據了。再看下面的例子:

typedef strUCt mydef{

int a,b,c,d;

int array[10];

}mydef;

?

mydef func();

int main()

{

mydef d;

d=func();

}

mydef func()

{

mydef d;

return d;

}

接著看反匯編的代碼:

00000000 <.data>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 48????????????????????sub????$0x48,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????8d 45 b8????????????????????lea????0xffffffb8(%ebp),%eax

??13:??????????83 ec 0c?????????????????????sub????$0xc,%esp

??16:??????????50????????????????????????push???%eax

??17:??????????e8 06 00 00 00????????????????call???0x22

??1c:?83 c4 0c????????????????????add????$0xc,%esp

??1f:?c9????????????????????????leave?

??20:??????????c3????????????????????????ret???

??21:??????????90????????????????????????nop???

??22:??????????55????????????????????????push???%ebp

??23:??????????89 e5??????????????????????mov????%esp,%ebp

??25:??????????57????????????????????????push???%edi

??26:??????????56????????????????????????push???%esi

??27:??????????83 ec 40????????????????????sub????$0x40,%esp

??2a:?8b 7d 08????????????????????mov????0x8(%ebp),%edi

??2d:??????????8d 75 b8????????????????????lea????0xffffffb8(%ebp),%esi

??30:??????????fc?????????????????????????cld???

??31:??????????b8 0e 00 00 00????????????????mov????$0xe,%eax

??36:??????????89 c1??????????????????????mov????%eax,%ecx

??38:??????????f3 a5???????????????????????repz movsl %ds:(%esi),%es:(%edi)

??3a:?8b 45 08????????????????????mov????0x8(%ebp),%eax

??3d:??????????83 c4 40????????????????????add????$0x40,%esp

??40:??????????5e????????????????????????pop????%esi

??41:??????????5f????????????????????????pop????%edi

??42:??????????c9????????????????????????leave?

??43:??????????c2 04 00????????????????????ret????$0x4

我們自定義的結構為0x38Bytes,gcc為了保持堆棧的16Bytes對齊,分配了0x40Bytes的空間。函數func并沒有參數,但是在調用時,卻將變量d的指針傳了進去。然后利用這個指針,使用指令movsl直接對d進行賦值。再看下面的例子:

typedef struct mydef{

int a,b,c,d;

int array[10];

}mydef;

?

mydef func();

int main()

{

func();

}

mydef func()

{

mydef d;

return d;

}

再看反匯編的結果:

00000000 <.data>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 48????????????????????sub????$0x48,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????8d 45 b8????????????????????lea????0xffffffb8(%ebp),%eax

??13:??????????83 ec 0c?????????????????????sub????$0xc,%esp

??16:??????????50????????????????????????push???%eax

??17:??????????e8 06 00 00 00????????????????call???0x22

??1c:?83 c4 0c????????????????????add????$0xc,%esp

??1f:?c9????????????????????????leave?

??20:??????????c3????????????????????????ret???

??21:??????????90????????????????????????nop???

??22:??????????55????????????????????????push???%ebp

??23:??????????89 e5??????????????????????mov????%esp,%ebp

??25:??????????57????????????????????????push???%edi

??26:??????????56????????????????????????push???%esi

??27:??????????83 ec 40????????????????????sub????$0x40,%esp

??2a:?8b 7d 08????????????????????mov????0x8(%ebp),%edi

??2d:??????????8d 75 b8????????????????????lea????0xffffffb8(%ebp),%esi

??30:??????????fc?????????????????????????cld???

??31:??????????b8 0e 00 00 00????????????????mov????$0xe,%eax

??36:??????????89 c1??????????????????????mov????%eax,%ecx

??38:??????????f3 a5???????????????????????repz movsl %ds:(%esi),%es:(%edi)

??3a:?8b 45 08????????????????????mov????0x8(%ebp),%eax

??3d:??????????83 c4 40????????????????????add????$0x40,%esp

??40:??????????5e????????????????????????pop????%esi

??41:??????????5f????????????????????????pop????%edi

??42:??????????c9????????????????????????leave?

??43:??????????c2 04 00????????????????????ret????$0x4

可以說,與上面的結果一字不差!我們沒有在main函數中聲明變量存儲func返回的結果,但是gcc替我們做了。它仍然為函數func傳遞了一個指針,并將結果傳了出來,盡管我們對返回值不感興趣,但編譯器對我們的興趣好像也沒有興趣,依然我行我素。(如果使用了優化選項,結果很可能有所相同)。

8.?????????給函數傳遞參數

gcc遵循一般的c語言標準,包括參數傳遞方式。看看下面的例子:

char res;

char func(char a,char b);

int main()

{

res=func(0x02,0x03);

}

char func(char a,char b)

{

return a+b;

}

再看看他的反匯編代碼:

00000000 <.data>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????83 ec 08????????????????????sub????$0x8,%esp

??13:??????????6a 03??????????????????????push???$0x3

??15:??????????6a 02??????????????????????push???$0x2

??17:??????????e8 0a 00 00 00????????????????call???0x26

??1c:?83 c4 10????????????????????add????$0x10,%esp

??1f:?a2 44 00 00 00????????????????mov????%al,0x44

??24:??????????c9????????????????????????leave?

??25:??????????c3????????????????????????ret???

??26:??????????55????????????????????????push???%ebp

??27:??????????89 e5??????????????????????mov????%esp,%ebp

??29:??????????83 ec 04????????????????????sub????$0x4,%esp

??2c:?8b 45 08????????????????????mov????0x8(%ebp),%eax

??2f:?8b 55 0c????????????????????mov????0xc(%ebp),%edx

??32:??????????88 45 ff?????????????????????mov????%al,0xffffffff(%ebp)

??35:??????????88 55 fe?????????????????????mov????%dl,0xfffffffe(%ebp)

??38:??????????8a 45 fe?????????????????????mov????0xfffffffe(%ebp),%al

??3b:??????????02 45 ff?????????????????????add????0xffffffff(%ebp),%al

??3e:?0f be c0?????????????????????movsbl %al,%eax

??41:??????????c9????????????????????????leave?

??42:??????????c3????????????????????????ret???

如果你精通匯編語言,看完這段代碼,恐怕你已經口吐鮮血并暈倒在地了!gcc居然生成了這么啰嗦的代碼!但是,我們還是先說說C語言的函數調用規范吧。

我們已經看到了,參數從右到左依次入棧。下面的說明全部以32Bytes代碼為準,其規范具體可羅列以下幾條:

l??????????調用者負責將參數壓入堆棧,順序為從右到左依次入棧。也就是左邊的最后入棧。

l??????????調用者使用near call指令將控制權傳給被調用者。

l??????????被調用者得到控制權,一般需要創建堆棧框架(這不是必需的,通常都是這么做)。首先,將ebp壓入堆棧保存,再將esp放入ebp,使ebp成為訪問參數的基址指針。

l??????????被調用者通過ebp訪問參數。因為ebp已經先行壓入堆棧,所以[ebp+4]就是被call指令自動壓入堆棧的返回地址,顯然,從[ebp+8]開始,就是參數。由于函數最左邊的參數最后被壓入堆棧,所以[ebp+8]就是該參數,其他參數以此類推。像printf這樣的函數,具有個數不確定的參數,但是參數入棧順序的規則,說明被調用者通過[ebp+8]就能夠找到第一個參數,其他參數的類型和數目,則需要由第一個參數給出。

l??????????被調用者減小esp的值為堆棧中的臨時變量分配空間,然后使用ebp和一個負的偏移訪問。

l??????????被調用者使用al,ax,eax返回大小不同的值。浮點數可以通過ST0寄存器返回。

l??????????被調用者完成處理后,使用事先建立的堆棧框架,恢復esp,sbp的值,并使用ret指令返回調用者。

l??????????調用者重新得到控制權,通過給esp加上一個立即數清空堆棧(盡量不要使用多次pop指令清空堆棧)。如果因為使用了錯誤的函數原型通過堆棧多傳遞了或者少傳遞了參數,調用者仍然能夠將堆棧恢復到正確的狀態,因為調用者知道自己向堆棧壓了幾個字節的數據。

結合C語言的函數調用規則,上面的代碼不難理解。

從80386開始,push指令的操作數可以是8-bit,16-bit,32-bit,但是C語言統統按32-bit整型數處理,被調用者也按32-bit進行處理。這一點很重要,特別是匯編語言和C語言混合編程時。

9.?????????基本的數據類型間的轉換

gcc處理三類基本數據類型:

l??????????signed char , unsigned char??, 1 Byte

l??????????signed short , unsigned short , 2 Bytes

l??????????signed int , unsigned int , 4 Bytes

各種數據類型間的轉換,遵循一般C語言的規則,具體可以參考IA-32的標準。這里只舉一例說明:

int main()

{

char ch=’a’;

int x=2;

int y=-4;

}

使用同樣的方法進行編譯及反匯編:

00000000 <main>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 18????????????????????sub????$0x18,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????c6 45 ff 61???????????????????moVB???$0x61,0xffffffff(%ebp)

??14:??????????c7 45 f8 02 00 00 00???movl???$0x2,0xfffffff8(%ebp)

??1b:??????????c7 45 f4 fc ff ff ff????????movl???$0xfffffffc,0xfffffff4(%ebp)

??22:??????????c9????????????????????????leave?

??23:??????????c3????????????????????????ret???

?

10.?????gcc編譯代碼的基本運行環境

這一部分,我查了很多的文檔,都沒有這方面的介紹。又請教了很多的高手,大致情況如下,我實在無法保證這里所說的都是正確的,并且將來也是正確的,僅供參考:

l??????????32-bit的保護模式下運行。

l??????????段寄存器CS,ds,es,fs,gs,ss必須指向同一段內存區域。

l??????????沒有初始化的全局變量被放在BSS的段內,該區域在代碼段之后。但是,如果你生成的文件是二進制文件,BSS段不是該文件的一部分,你需要自己小心使用。初始化的全局變量在DATA段內,它是二進制文件的一部分,并且位于代碼段之后。被聲明為const的全局變量被放在RODATA段內,它也是二進制文件的一部分,并放在代碼段之后。

l??????????確保堆棧沒有溢出,小心代碼段和全局數據不要被破壞。

我也查了Intel提供的幫助文檔“Intel Architecture Software Developer’s Manual”,一共有三卷之多!參考了其中關于內存組織(Volume 1:Memory Organization)中的說法(建議你去好好研究)。總之,使cs,ds,ss總是指向同一內存區域應該可以使代碼正確運行。如果運行環境不是這樣,我就不知道結果了。

11.?????訪問外部的全局變量

看看在非C語言程序中如何訪問C語言程序中的全局變量。如果你想使用其他程序加載C程序,例如匯編語言寫的程序,這部分很有用,特別是在核心開發時經常用到。

int myVal=0x5;

int main()

{

}

編譯這段代碼:

gcc –c test.c

ld –Ttext 0x0 –e main –N –oformat binary –Map memmap.txt –o test.bin test.o

objdump –D –b binrary –m i386 test.bin

得到如下結果:

00000000 <.data>:

???0:??????????55????????????????????????push???%ebp

???1:??????????89 e5??????????????????????mov????%esp,%ebp

???3:??????????83 ec 08????????????????????sub????$0x8,%esp

???6:??????????83 e4 f0?????????????????????and????$0xfffffff0,%esp

???9:??????????b8 00 00 00 00????????????????mov????$0x0,%eax

???e:?29 c4??????????????????????sub????%eax,%esp

??10:??????????c9????????????????????????leave?

??11:??????????c3????????????????????????ret???

??12:??????????00 00??????????????????????add????%al,(%eax)

??14:??????????05????????????????????????.byte 0x5

??15:??????????00 00??????????????????????add????%al,(%eax)

全局變量myVal存儲在0x14。剛才已經使用-Map開關使ld生成了內存映像文件memmap.txt,應該能夠找到:

.data???????????0x00000014????????0x4

?*(.data .data.* .gnu.linkonce.d.*)

?.data??????????0x00000014????????0x4 test.o

????????????????0x00000014????????????????myVal

說明myVal位于test.o模塊的0x00000014位置。使用地址作為偏移量,就可以直接在其他語言中訪問myVal變量了。另為也可以通過memmap.txt查到BSS段的大小:

cat memmap.txt grep ‘/.bss’ grep ‘0x’ sed ‘s/.*0x/0x/’

本例子,BSS的大小是0x0。

無法直接訪問C程序中的使用static修飾的全局變量。因為這樣的變量是靜態的,map文件中沒有列出他們的地址。也許你可以使用其他辦法做到,但是,最好不要這樣做。

12.?????生成其他格式的二進制文件的選項

生成不同格式的二進制文件是一件相當麻煩的事。它需要使用很多不常使用的選項,并且有些在man的幫助信息中沒有被列出。

首先是gcc的選項:-nostdinc。很顯然,使用該選項后,gcc就不搜索默認的include路徑了,通常是/usr/include。如果需要使用的自定義的頭文件,可以使用-I選項添加搜索路徑。

然后是ld的選項。第一個是-nostdlib,就是忽略標準庫。如果需要,可以使用-L選項指定庫的搜索路徑。第二個是-Ttext,就是指定代碼段的地址,如果沒有繼續指定其他段的地址,則他們將自動的一次被放在代碼段之后。第三個是-e,就是指定代碼的入口地址,默認的是_start,如果代碼不是以其開頭,就應該指定入口點。第四個是—oformat binary,就是輸出的文件是原始的二進制文件,而是如文件可以使系統支持的任何文件。但是,中間模塊文件不能是原始的二進制文件,因為還需要很多符號和重定位信息。可以使用—iformat選項指定輸入文件的格式,但通常很少使用。第五個是-static,如果使用了其他庫,用該使用靜態鏈接方式,除非你的程序支持動態鏈接。

另外還有代碼指示偽指令。匯編器可以編譯16-bit代碼,也可以編譯32-bit代碼。但是,gcc總是生成32-bit的匯編代碼。通過在C代碼中使用asm()偽指令可以讓gcc生成16-bit匯編代碼。

第一個是.code16,即生成在16-bit段中運行的16-bit代碼;

第二個是.code32,即生成在32-bit段中運行的32-bit代碼,默認情況下gcc總是這么做;

第三個是.code16gcc,gcc將根據需要決定生成在16-bit段下運行的16-bit或32-bit代碼。GAS將會加上必要的前綴,指示32-bit的指令或寄存器等。這個選項是很有用的,它允許我們使用C語言寫在16-bit環境下運行的代碼,不論是實模式還是保護模式。

現在可以在一個C模塊中既有16-bit代碼,又有32-bit代碼,但是此時需要注意不同部分代碼的地址空間問題。

例如,我們想使用gcc生成在DOS下運行的.com程序和啟動引導程序。

首先,DOS中的.com文件是在實模式下運行的原始的二進制文件,其起始地址為0x100。要使用gcc生成.com文件,在每一個.c文件的開頭加上如下偽指令:

__asm__(“code16gcc/n”);

如果需要引用其他庫文件,則這些庫文件也需要按這種方式生成。在鏈接時,加上如下選項:

-Ttext 0x100 –static –oformat binary

如果程序中包含嵌入的匯編代碼,需要將其轉換為AT&T格式。

如果要寫引導程序,只需要在鏈接時使用0x7C00代替0x100!另外,最終生成的二進制代碼必須小于446個字節!

13.?????參考資料

l??????????Intel Architecture Software Developer’s Manual

l??????????Manual Pages in Linux

l??????????Redhat GNUPro Toolkit


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/454190.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/454190.shtml
英文地址,請注明出處:http://en.pswp.cn/news/454190.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

TensorFlow 實例一:線性回歸模型

代碼 # -- encoding:utf-8 -- """ Create by ibf on 2018/5/6 """import numpy as np import tensorflow as tf# 1. 構造一個數據 np.random.seed(28) N 100 x np.linspace(0, 6, N) np.random.normal(loc0.0, scale2, sizeN) y 14 * x - …

python后端數據發送到前端_Python Django 前后端數據交互 之 后端向前端發送數據...

Django 從后臺往前臺傳遞數據時有多種方法可以實現。最簡單的后臺是這樣的&#xff1a;from django.shortcuts import renderdefmain_page(request):return render(request, ‘index.html‘)這個就是返回index.html的內容&#xff0c;但是如果要帶一些數據一起傳給前臺的話&…

Dapper的基本使用

Dapper是.NET下一個micro的ORM&#xff0c;它和Entity Framework或Nhibnate不同&#xff0c;屬于輕量級的&#xff0c;并且是半自動的。也就是說實體類都要自己寫。它沒有復雜的配置文件&#xff0c;一個單文件就可以了。給出官方地址。 http://code.google.com/p/dapper-dot-n…

函數名作為參數傳遞

假如不知道signal的函數原型&#xff0c;考慮child_handler函數的參數從哪里來&#xff1f; void child_handler(int sig) { if (sig SIGINT) kill(pid_parent, SIGUSR1); } int main(void) { ...... signal(SIGINT, child_handler); ...... } 1、…

易語言神經網絡驗證碼識別_遞歸神經網絡 GRU+CTC+CNN 教會驗證碼識別

利用 NLP 技術做簡單數據可視化分析Chat 簡介&#xff1a;用遞歸神經網絡采用端到端識別圖片文字&#xff0c;遞歸神經網絡大家最早用 RNN &#xff0c;缺陷造成梯度消失問題&#xff1b;然后采用了 LSTM&#xff0c;解決 RNN 問題&#xff0c;并且大大提高準確率&#xff1b;現…

GCC 生成的符號表調試信息剖析

GCC把C語言源文件&#xff08;&#xff07;.c&#xff07;&#xff09;編譯成匯編語言文件&#xff08;&#xff07;.s&#xff07;&#xff09;&#xff0c;匯編器把匯編語言文件翻譯成目標文件&#xff08;&#xff07;.o&#xff07;&#xff09;&#xff0c;最后由鏈接器鏈…

《操作系統》OS學習(一):OS相關

清華大學操作系統OS&#xff08;向勇、陳渝&#xff09;視頻地址&#xff1a;http://www.xuetangx.com/courses/course-v1:TsinghuaX30240243Xsp/about 在ucore實驗中&#xff0c;一些基本的常用工具如下&#xff1a; 命令行shell: bash shell -- 有對文件和目錄操作的各種命令…

Android4.0藍牙使能的詳細解析

毫無疑問&#xff0c;bluetooth的打開是在Settings中進行的操作。因此&#xff0c;冤有頭&#xff0c;債有主&#xff0c;我們來到了Settings.java中&#xff0c;果然發現了相關的代碼如下&#xff1a; mBluetoothEnabler new BluetoothEnabler(context, new Switch(context));…

第一次沖刺

本人小組分工角色&#xff1a;產品負責人 本組沖刺訂單介紹&#xff1a;經過小組的成員討論&#xff0c;我們大概確立了一個沖刺的訂單是完成一個簡易的長沙學院網站項目。 最后完成情況概述&#xff1a;經過大約兩周的努力&#xff0c;我們組的編程人員運用python構建了一個簡…

移動端導出excel_連載系列【4】Excel開發移動端quot;APPquot;

前三篇文章介紹了百度地圖生成器、源代碼編輯器、GPS經緯度批量轉換工具、源代碼編輯器中添加自定義功能按鈕和地圖控件。這些寫好的Java Script代碼雖然可以實現所有期望的結果&#xff0c;但畢竟不是一個HTML文件&#xff0c;不便于傳播和使用&#xff0c;更無法變成一個類似…

《操作系統》OS學習(二):啟動、中斷、異常

Bootloader:加載OS。操作系統一開始是放在DISK&#xff08;硬盤&#xff09;中&#xff0c;并不是放在內存中。 BIOS&#xff1a;基本I/O處理系統。存放在ROMRead-Only Memory&#xff09;只讀存儲中 BIOS&#xff08;Basic Input/Output System&#xff09;基本輸入輸出系統。…

[GCC for C]編譯選項---IDE掩蓋下的天空

編譯選項 ---------IDE掩蓋下的天空 /*************************************** * gcc for c language ***************************************/ Single Source to Executable $ gcc helloworld.c [-o howdy] 默認生成的名字a.exe ______________________________________ …

2016級算法第二次上機-F.ModricWang's Number Theory II

891 ModricWangs Number Theory II 思路 使得序列的最大公約數不為1&#xff0c;就是大于等于2&#xff0c;就是找到一個大于等于2的數&#xff0c;它能夠整除序列中的所有數。 考慮使得一個數d整除數組中所有數的代價&#xff1a; 如果一個數不能被b整除&#xff0c;那么可以花…

常用css屬性集(持續更新…)

禁止換行&#xff0c;超出部分顯示…&#xff1a;a. 代碼&#xff1a;.hide_word{ max-width: 100px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } b. 效果&#xff1a; 本文轉自 bilinyee博客&#xff0c;原文鏈接&#xff1a; http://blog.51cto.co…

parallels網絡初始化失敗_33 個神經網絡「煉丹」技巧

自然語言處理Andrej Karpathy 是深度學習計算機視覺領域、與領域的研究員。博士期間師從李飛飛。在讀博期間&#xff0c;兩次在谷歌實習&#xff0c;研究在 Youtube 視頻上的大規模特征學習&#xff0c;2015 年在 DeepMind 實習&#xff0c;研究深度強化學習。畢業后&#xff0…

《操作系統》OS學習(三):系統調用

例子 首先看一個標準C庫的例子&#xff1a;當我們程序中使用了C庫中的printf()函數&#xff0c;實際在底層是在內核態中調用了write()函數。圖中右側則是將程序代碼與C庫都算到應用程序中&#xff0c;內核提供了一個系統調用接口。 從這個例子我們可以得到以下幾點&#xff1a…

cygwin/gcc與MinGW

cygwin/gcc和MinGW都是gcc在windows下的編譯環境&#xff0c;但是它們有什么區別?在實際工作中如何選擇這兩種編譯器呢?cygwin/gcc完全可以和在linux下的gcc劃等號&#xff0c;這個從boost庫的劃分中就可以看出來端倪&#xff0c;cygwin下的gcc和linux下的gcc使用的是相同的T…

JavaScript服務器端開發技術(對象屬性的枚舉與查詢)

既然對象是屬性的集合&#xff0c;那么檢測與枚舉集合中的屬性就是一項重要任務。對此&#xff0c;我們來分別看一下ES3和ES5提供的解決方案。 1) ES3枚舉方案 示例代碼&#xff1a; var contacts{ ID:[0,1,2,3,4,5], names:["Zero","One","Two&q…

treelistview 所有節點失去焦點_垃圾詢盤過濾,焦點科技的 Milvus 實踐

文章作者&#xff1a;黎陽&#xff0c;焦點科技軟件開發工程師李成龍&#xff0c;Zilliz 數據工程師Milvus (https://milvus.io/) 向量搜索引擎開源半年以來&#xff0c;全球已經有數百家企業或組織用戶。焦點科技是一家以 B2B 外貿交易為主營業務的科技公司&#xff0c;也是 M…

《操作系統》OS學習(四):計算機體系結構、內存層次和地址生成

計算機除了計算能力之外還有存儲能力&#xff0c;存儲能力即計算機擁有一系列的存儲介質&#xff0c;我們可以在存儲介質上存儲我們的代碼和數據。計算機體系結構中約定了哪些地方可以用來存儲數據&#xff1a;CPU內的寄存器、內存和外存。不同的存儲介質&#xff0c;容量、速度…