|
[ random @ 10.10.2002. 17:00 ] @
| Code: random@galeb:~/code/test$ gcc -g -o prazna prazna.c
random@galeb:~/code/test$ gdb prazna
GNU gdb 5.0
...
(gdb) list main
1 int main() {
2 return 0;
3 }
(gdb) disass main
Dump of assembler code for function main:
0x804839c <main>: push %ebp
0x804839d <main+1>: mov %esp,%ebp
0x804839f <main+3>: xor %eax,%eax
[b]0x80483a1 <main+5>: jmp 0x80483a4 <main+8>
0x80483a3 <main+7>: nop [/b]
0x80483a4 <main+8>: leave
0x80483a5 <main+9>: ret
End of assembler dump.
(gdb)
Zašto kompajler, kada se ne navode swithcevi za optimizaciju, generiše beskoristan markiran kod?
Podpitanje: zašto se prazni EAX registar pre izlaska iz funkcije?
Testirano na Linuxu na dual P2 mašini i na FreeBSD-u na P1 i P2 mašinama. |
[ Ivan Dimkovic @ 10.10.2002. 17:19 ] @
A propo nopova, generalno ne mora da bude sporiji, nop-ovi se ubacuju u slucaju da se tako postize bolji flow kroz pipeline i paralelizaciju instrukcija. Mada u ovom konkretnom slucaju to zanemari, jer nema logike da se tako nesto stavlja, nema ni koda :)
A praznjenje eax-a je valjda zbog "return 0" jer je xor eax, eax isto sto i mov eax, 0 a zahteva jedan ciklus manje :) Zameni "return 0" u "return 1" i videces da ce u tom slucaju biti "mov eax, 1"
Sto se jmp-a tice, ovo mi stvarno nije jasno... jedino sto mi pada na pamet je da je ovo default epilog koji gcc stavlja u svaku funckiju zbog nekih akrobacija ako funkcija ima kondicionalno vracanje vise mogucih varijabli.. pa sam patchuje kod - mada mi sve to glupo zvuci, verovatno nije ni tacno.
Probaj da iskompajliras sa Intelovim ICL kompajlerom sa max. optimizacijama pa uporedi kod.
MSVC generise:
Code:
_main PROC NEAR ; COMDAT
; 3 : return 0;
xor eax, eax
; 4 : }
ret 0
[ random @ 10.10.2002. 19:15 ] @
To je i meni palo prvo na pamet, da je razlog izbegavanje grešaka pri paralelnom izvršavanju instrukcija, ali to ne drži vodu, obzirom da moderni procesori imaju a) algoritme za predikciju grananja i ispravljanje grešaka pri paralelnom izvršavanju b) mnogo više od dve jedinice za paralelno izvršavanje instrukcija (npr. Athlon valjda ima 7), pa se efektivno ništa ne dobija.
Inače optimizovan binary (sa -O ili jače) nema dotičnu JMP instrukciju.
Code: push %ebp
mov %esp,%ebp
xor %eax,%eax
leave
ret
P.S. Da, tačno, u EAX je povratna vrednost programa, nisam razmišljao u trenutku pisanja.
[ Ivan Dimkovic @ 10.10.2002. 22:10 ] @
Nista... onda ostaje jedino objasnjenje za taj jmp/nop
da bi kod bio 31337 - GNU C 0w3 YoU@#^
:) Nista pametnije mi ne pada na pamet...
[ leka @ 11.10.2002. 00:55 ] @
Ja priznajem da pojma nemam o asembleru ali imam nesto malo pojma o ELF-u, tako da pretpostavljam da GCC na taj nacin "priprema" program za ELF format...
Code:
[root@pug /root]# gcc -g -o test test.c
[root@pug /root]# file test
test: ELF 32-bit LSB executable, Intel 80386, version 1, dynamically linked (uses shared libs), not stripped
Mozda lupam, mozda ne...
[ Ivan Dimkovic @ 11.10.2002. 07:10 ] @
Ne verujem, output executable format je stvar linkera a ne kompajlera, a cak i da postoji neki zahtev za aligment-om to bi uradio linker koji bi uradio zero padding na 1/2/4/8/16K boundary a ne kompajler sa besmislenim kodom :)
Sa stanovista CPU-a je krajnje nebitno koji je exec. format, bitno je da se kod ucita i mapira na neku memorijsku lokaciju. A i obrnuto, executable format ne zanima da li funkcija ima neki logican kod ili ne.
Uostalom, ako stavis max optimizaciju besmisleni kod nestaje a format je i dalje ELF, tako da to sigurno nije.
[ Mikky @ 12.10.2002. 00:39 ] @
Citat: Ivan Dimkovic:
A propo nopova, generalno ne mora da bude sporiji, nop-ovi se ubacuju u slucaju da se tako postize bolji flow kroz pipeline i paralelizaciju instrukcija. Mada u ovom konkretnom slucaju to zanemari, jer nema logike da se tako nesto stavlja, nema ni koda :)
kako to "nop postize bolji flow kroz pipeline i paralelizaciju instrukcija"?
ma sta to znacilo :)
gde se moze vise saznati o ovim stvarima?
[ Ivan Dimkovic @ 12.10.2002. 08:07 ] @
Pa kao sto random rece - na novim Intel/AMD masinama se to radi manje-vise automatski, ali neke DSP arhitekture i malo stariji x86 procesori imaju dobit. Ovim se izbegava tzv. "pipeline stall" kada procesor paralelizuje izvrsavanje nekih instrukcija i kada dodje do "trke" - tj. kad sledeca instrukcija ceka na rezultat prosle. Moderni CPU-ovi imaju bafere i jos dosta alatki za izbegavanje "kocenja"
http://www.cim.mcgill.ca/~fran...-304-427/messages/node104.html
Takodje, i redosled instrukcija moze igrati vrlo veliku ulogu. Intel kompajleri imaju citav set optimizacionih tehnologija koji otklanjaju "uska grla" u x86 kodu, a postoji i alat VTune koji ce ukazati na svaki potencijalni bottleneck (flow dependency, unaligned access, loop unrolling mogucnost, SSE/MMX mogucnosti, itd..)
No ovo je skroz offtopic - nema veze sa besmislenim kodom koji GCC ubacuje u epilog funkcije..
[ tOwk @ 12.10.2002. 10:04 ] @
Citat: 10:43:52 @ ~/tmp > gcc -c -g -o aproba aproba.c
10:44:05 @ ~/tmp > gdb aproba
...
(gdb) list main
1 int main() {
2 return 0;
3 }
(gdb) disass aproba
No symbol "aproba" in current context.
(gdb) disass main
Dump of assembler code for function main:
0x0 <main>: push %ebp
0x1 <main+1>: mov %esp,%ebp
0x3 <main+3>: xor %eax,%eax
0x5 <main+5>: jmp 0x7 <main+7>
0x7 <main+7>: leave
0x8 <main+8>: ret
NOP-a nema na dvoprocesorskoj PIII mašini (isto je i bez ,,-c'', odnosno kada se i linkuje).
A zašto se ubacuje (odnosno koristi) JMP instrukcija, mislim da objašnjava sledeće:
Citat:
(gdb) list main
1 int main() {
2 if (1==0)
3 return 0;
4 else return 1;
5 }
(gdb) disass main
Dump of assembler code for function main:
0x0 <main>: push %ebp
0x1 <main+1>: mov %esp,%ebp
0x3 <main+3>: jmp 0x10 <main+16>
0x5 <main+5>: xor %eax,%eax
0x7 <main+7>: jmp 0x17 <main+23>
0x9 <main+9>: jmp 0x17 <main+23>
0xb <main+11>: nop
0xc <main+12>: lea 0x0(%esi,1),%esi
0x10 <main+16>: mov $0x1,%eax
0x15 <main+21>: jmp 0x17 <main+23>
0x17 <main+23>: leave
0x18 <main+24>: ret
Znači, JMP se koristi da se obezbedi ,,Jedna Tačka Izlaska'' (znači da nemamo 100 leave/ret-ova po funkciji). To izgleda sasvim logično kada imamo sve više grananja, a ove jednostavne probleme i optimizacija rešava u prvom stupnju (optimizovan, ovaj je iste veličine kao i prethodni, samo vraća 1 umesto 0).
Znači, čini mi se da se radi o najopštijem slučaju (a optimizacija je zadužena za ostalo).
Naravno, ovo je samo moja procena, a šta se zapravo dešava ne znam. Znam da stariji GCC (2.8) očekuje od asemblera da ubaci NOP ,,zbog grananja'' (citat sa http://www.cag.lcs.mit.edu/raw/memo/10/rgcc.html, nije verodostojan).
A osim vaših objašnjenja (o dopunjavanju radi istovremenog izvršavanja većeg broja instrukcija), i ovog gore nepouzdanog citata, nemam druge ideje.
Pozdrav
PS. Upotreba ,,gcc -S'' (asembler izlaz) navodi na misao da se neka poravnjavanja ipak odigravaju (align...). Ko bolje zna od mene, više će mu i kod reći.
[ leka @ 13.10.2002. 18:29 ] @
tOwk, hvala na ovom objasnjenju, malo smo svi naucili neke zanimljive stvari. :) Mada obicnom programeru ove stvari ne trebaju, to je svima jasno.
[ Ivan Dimkovic @ 13.10.2002. 18:46 ] @
Pa dobro, malo znanja o procesoru i kompajleru nikom nije na odmet - cak i da se taj neko ne bavi optimizacijom koda :)
Copyright (C) 2001-2025 by www.elitesecurity.org. All rights reserved.
|