Jak uzyskać wyjście asemblera ze źródła C / C ++ w gcc?


Odpowiedzi:


420

Użyj -Sopcji gcc (lub g ++).

gcc -S helloworld.c

Spowoduje to uruchomienie preprocesora (cpp) na helloworld.c, wykonanie wstępnej kompilacji, a następnie zatrzymanie przed uruchomieniem asemblera.

Domyślnie spowoduje to wyświetlenie pliku helloworld.s. Plik wyjściowy można nadal ustawić za pomocą -oopcji.

gcc -S -o my_asm_output.s helloworld.c

Oczywiście działa to tylko wtedy, gdy masz oryginalne źródło. Alternatywą, jeśli masz tylko wynikowy plik obiektowy, jest użycie objdump, ustawiając --disassembleopcję (lub -dw formie skróconej).

objdump -S --disassemble helloworld > helloworld.dump

Ta opcja działa najlepiej, jeśli dla pliku obiektowego ( -gw czasie kompilacji) włączona jest opcja debugowania, a plik nie został usunięty.

Uruchomienie file helloworldda ci pewne wskazówki co do poziomu szczegółowości, który uzyskasz za pomocą objdump.


3
Chociaż jest to poprawne, wyniki z odpowiedzi Cr McDonougha były bardziej przydatne.
Rhys Ulerich,

3
dodatkowe użycie: objdump -M intel -S --disassemble helloworld> helloworld.dump, aby uzyskać zrzut obiektu w składni intel kompatybilnej z nasm na Linuksie.
touchStone 11.03.15

2
Jeśli masz jedną funkcję do optymalizacji / sprawdzania, możesz spróbować interaktywnych kompilatorów C ++ online, tj. Godbolt
fiorentinoing

1
@touchStone: GAS nie.intel_syntax jest kompatybilny z NASM . Jest bardziej podobny do MASM (np. mov eax, symbolJest obciążeniem, w przeciwieństwie do NASM, gdzie jest mov r32, imm32adresem), ale też nie jest w pełni kompatybilny z MASM. Bardzo polecam to jako przyjemny format do czytania, szczególnie jeśli lubisz pisać w składni NASM. objdump -drwC -Mintel | lesslub gcc foo.c -O1 -fverbose-asm -masm=intel -S -o- | lesssą przydatne. (Zobacz także Jak usunąć „szum” z danych wyjściowych zespołu GCC / clang? ). -masm=inteldziała również z clang.
Peter Cordes,

3
Lepsze wykorzystaniegcc -O -fverbose-asm -S
Basile Starynkevitch

173

Spowoduje to wygenerowanie kodu zestawu z przeplatanym kodem C + numery linii, aby łatwiej zobaczyć, które linie generują jaki kod:

# create assembler code:
g++ -S -fverbose-asm -g -O2 test.cc -o test.s
# create asm interlaced with source lines:
as -alhnd test.s > test.lst

Znalezione w Algorytmy dla programistów , strona 3 (co stanowi ogólną 15 stronę pliku PDF).


3
(To tak naprawdę na stronie (ponumerowanej) 3 (która jest 15. stroną pliku PDF))
Grumdrig,

1
Niestety asw systemie OS X te flagi nie są znane. Gdyby tak było, prawdopodobnie można by to zrobić w jednym wierszu, używając -Waopcji do przekazania opcji as.
Grumdrig,

23
g++ -g -O0 -c -fverbose-asm -Wa,-adhln test.cpp > test.lstbyłaby to krótka wersja tego.
legends2k

4
Można również użyć gcc -c -g -Wa,-ahl=test.s test.calbogcc -c -g -Wa,-a,-ad test.c > test.txt
phuclv

1
Blogu tłumacząc to bardziej szczegółowo, łącznie z wersją jednego polecenia jak legendy i Lu'u pisał. Ale dlaczego -O0? Jest pełen ładunków / sklepów, które utrudniają śledzenie wartości i nie mówią nic o wydajności zoptymalizowanego kodu.
Peter Cordes,

51

Następująca linia poleceń pochodzi z bloga Christiana Garbina

g++ -g -O -Wa,-aslh horton_ex2_05.cpp >list.txt

Uruchomiłem G ++ z okna DOS na Win-XP, z procedurą zawierającą ukrytą obsadę

c:\gpp_code>g++ -g -O -Wa,-aslh horton_ex2_05.cpp >list.txt
horton_ex2_05.cpp: In function `int main()':
horton_ex2_05.cpp:92: warning: assignment to `int' from `double'

Dane wyjściowe są zestawione wygenerowany kod iterowany z oryginalnym kodem C ++ (kod C ++ jest wyświetlany jako komentarze w wygenerowanym strumieniu asm)

  16:horton_ex2_05.cpp **** using std::setw;
  17:horton_ex2_05.cpp ****
  18:horton_ex2_05.cpp **** void disp_Time_Line (void);
  19:horton_ex2_05.cpp ****
  20:horton_ex2_05.cpp **** int main(void)
  21:horton_ex2_05.cpp **** {
 164                    %ebp
 165                            subl $128,%esp
?GAS LISTING C:\DOCUME~1\CRAIGM~1\LOCALS~1\Temp\ccx52rCc.s
166 0128 55                    call ___main
167 0129 89E5          .stabn 68,0,21,LM2-_main
168 012b 81EC8000      LM2:
168      0000
169 0131 E8000000      LBB2:
169      00
170                    .stabn 68,0,25,LM3-_main
171                    LM3:
172                            movl $0,-16(%ebp)

@Paladin - niekoniecznie. OP dotyczył uzyskania ekwiwalentu wyjściowego asemblera kodu źródłowego C / C ++, uzyskuje to Listing, który, jak się zgadzam, jest bardziej przydatny do zrozumienia, co robi kompilator i optymalizator. Spowodowałoby to jednak, że asembler zbankrutował, ponieważ nie spodziewał się numerów linii i skompilował bajty poza instrukcją asemblera.
Jesse Chisholm,

-O2Jeśli chcesz zobaczyć, jak gcc optymalizuje kod, użyj przynajmniej lub jakiejkolwiek opcji optymalizacji, której faktycznie używasz podczas budowania projektu. (Lub jeśli używasz LTO, tak jak powinieneś, to musisz zdemontować wyjście linkera, aby zobaczyć, co naprawdę dostajesz.)
Peter Cordes

27

Użyj przełącznika -S

g++ -S main.cpp

lub także z gcc

gcc -S main.c

Patrz także to


7
Sprawdź często zadawane pytania: „Można również zadawać własne pytania dotyczące programowania i odpowiadać na nie”. Chodzi o to, że teraz StackOverflow zawiera pytania i odpowiedzi jako zasób dla innych.
Steve Jessop

I może ktoś inny przyjdzie i zaskoczy cię lepszą odpowiedzią, choć czasami moja może być trochę gadatliwa ...
PhirePhly

Istnieje nawet odpowiedź na własny przycisk pytania.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

13

Jeśli to, co chcesz zobaczyć, zależy od połączenia wyniku, wówczas objdump na pliku wynikowym pliku / pliku wykonywalnym może być również przydatny jako dodatek do wyżej wspomnianego gcc -S. Oto bardzo przydatny skrypt autorstwa Lorena Merritta, który konwertuje domyślną składnię objdump na bardziej czytelną składnię nasm:

#!/usr/bin/perl -w
$ptr='(BYTE|WORD|DWORD|QWORD|XMMWORD) PTR ';
$reg='(?:[er]?(?:[abcd]x|[sd]i|[sb]p)|[abcd][hl]|r1?[0-589][dwb]?|mm[0-7]|xmm1?[0-9])';
open FH, '-|', '/usr/bin/objdump', '-w', '-M', 'intel', @ARGV or die;
$prev = "";
while(<FH>){
    if(/$ptr/o) {
        s/$ptr(\[[^\[\]]+\],$reg)/$2/o or
        s/($reg,)$ptr(\[[^\[\]]+\])/$1$3/o or
        s/$ptr/lc $1/oe;
    }
    if($prev =~ /\t(repz )?ret / and
       $_ =~ /\tnop |\txchg *ax,ax$/) {
       # drop this line
    } else {
       print $prev;
       $prev = $_;
    }
}
print $prev;
close FH;

Podejrzewam, że można tego również użyć na wyjściu gcc -S.


2
Mimo to ten skrypt to brudny hack, który nie do końca przekształca składnię. Np. mov eax,ds:0x804b794Nie jest bardzo NASMish. Czasami po prostu usuwa przydatne informacje: movzx eax,[edx+0x1]pozostawia czytelnikowi odgadnięcie, czy operand pamięci był, byteczy też word.
Ruslan,

Aby w pierwszej kolejności zdemontować składnię NASM, użyj Agner Fog'sobjconv . Możesz go zdemontować na standardowe wyjście z plikiem wyjściowym = /dev/stdout, dzięki czemu możesz przesyłać strumieniowo do lessoglądania. Jest też ndisasm, ale dezasembluje tylko płaskie pliki binarne i nie wie o plikach obiektowych (ELF / PE).
Peter Cordes,

9

Jak wszyscy zauważyli, skorzystaj z -Sopcji GCC. Chciałbym również dodać, że wyniki mogą się różnić (szalenie!) W zależności od tego, czy dodasz opcje optymalizacji (w -O0przypadku braku, w -O2przypadku agresywnej optymalizacji).

W szczególności na architekturach RISC kompilator często przekształca kod niemal nie do poznania w procesie optymalizacji. Spojrzenie na wyniki jest imponujące i fascynujące!


9

Jak wszyscy mówili, użyj opcji -S. Jeśli użyjesz opcji -save-temps, możesz również uzyskać wstępnie przetworzony plik ( .i), plik zestawu ( .s) i plik obiektowy (* .o). (pobierz każdy z nich, używając -E, -S i -c.)


8

Jak wspomniano wcześniej, spójrz na flagę -S.

Warto również przyjrzeć się rodzinie flag „-fdump-tree”, w szczególności „-fdump-tree-all”, która pozwala zobaczyć niektóre pośrednie formy gcc. Często mogą być bardziej czytelne niż asembler (przynajmniej dla mnie) i pozwalają zobaczyć, jak przebiegają optymalizacje.




8

-save-temps

Zostało to wspomniane na https://stackoverflow.com/a/17083009/895245 ale pozwólcie, że jeszcze to zilustruję.

Dużą zaletą tej opcji -S jest to, że bardzo łatwo jest dodać ją do dowolnego skryptu kompilacji, bez ingerowania w samą kompilację.

Kiedy to zrobisz:

gcc -save-temps -c -o main.o main.c

main.c

#define INC 1

int myfunc(int i) {
    return i + INC;
}

a teraz, poza normalnym wyjściem main.o, bieżący katalog roboczy zawiera również następujące pliki:

  • main.i jest premią i zawiera wstępnie przejęty plik:

    # 1 "main.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 31 "<command-line>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 32 "<command-line>" 2
    # 1 "main.c"
    
    
    int myfunc(int i) {
        return i + 1;
    }
  • main.s zawiera żądany wygenerowany zespół:

        .file   "main.c"
        .text
        .globl  myfunc
        .type   myfunc, @function
    myfunc:
    .LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    %edi, -4(%rbp)
        movl    -4(%rbp), %eax
        addl    $1, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
    .LFE0:
        .size   myfunc, .-myfunc
        .ident  "GCC: (Ubuntu 8.3.0-6ubuntu1) 8.3.0"
        .section    .note.GNU-stack,"",@progbits

Jeśli chcesz to zrobić dla dużej liczby plików, rozważ użycie zamiast tego:

 -save-temps=obj

który zapisuje pliki pośrednie w tym samym katalogu, co -oobiekt wyjściowy zamiast w bieżącym katalogu roboczym, unikając w ten sposób potencjalnych konfliktów nazw basename.

Kolejną fajną rzeczą w tej opcji jest dodanie -v:

gcc -save-temps -c -o main.o -v main.c

w rzeczywistości pokazuje jawne pliki używane zamiast brzydkich tymczasowych plików /tmp, więc łatwo jest dokładnie wiedzieć, co się dzieje, co obejmuje etapy wstępnego przetwarzania / kompilacji / montażu:

/usr/lib/gcc/x86_64-linux-gnu/8/cc1 -E -quiet -v -imultiarch x86_64-linux-gnu main.c -mtune=generic -march=x86-64 -fpch-preprocess -fstack-protector-strong -Wformat -Wformat-security -o main.i
/usr/lib/gcc/x86_64-linux-gnu/8/cc1 -fpreprocessed main.i -quiet -dumpbase main.c -mtune=generic -march=x86-64 -auxbase-strip main.o -version -fstack-protector-strong -Wformat -Wformat-security -o main.s
as -v --64 -o main.o main.s

Testowane w Ubuntu 19.04 amd64, GCC 8.3.0.




3

Dane wyjściowe tych komend

Oto kroki, aby zobaczyć / wydrukować kod asemblera dowolnego programu C w systemie Windows

konsola / terminal / wiersz poleceń:

  1. Napisz program C w edytorze kodu C, takim jak bloki kodu i zapisz go z rozszerzeniem .c

  2. Skompiluj i uruchom.

  3. Po pomyślnym uruchomieniu przejdź do folderu, w którym zainstalowałeś kompilator gcc i podaj

    następujące polecenie, aby uzyskać plik „.s” pliku „.c”

    C: \ gcc> gcc -S pełna ścieżka do pliku C. ENTER

    Przykładowe polecenie (jak w moim przypadku)

    C: \ gcc> gcc -SD: \ Aa_C_Certified \ alternate_letters.c

    Spowoduje to wygenerowanie pliku „.s” oryginalnego pliku „.c”

4 Następnie wpisz następujące polecenie

C; \ gcc> cpp filename.s ENTER

Przykładowe polecenie (jak w moim przypadku)

C; \ gcc> cpp alternate_letters.s

Spowoduje to wydrukowanie / wydrukowanie całego kodu języka asemblera twojego programu C.


2

Jako opcji użyj „-S”. Wyświetla dane wyjściowe zestawu w terminalu.


Aby wyświetlić w terminalu, użyj gcc foo.c -masm=intel -fverbose-asm -O3 -S -o- |less. -Ssam tworzy foo.s.
Peter Cordes

2

Niedawno chciałem poznać zestawienie każdej funkcji w programie.
Tak to zrobiłem.

$ gcc main.c                      // main.c source file
$ gdb a.exe                       // gdb a.out in linux
  (gdb) disass main               // note here main is a function
                                  // similary it can be done for other functions

2

Oto rozwiązanie dla C przy użyciu gcc:

gcc -S program.c && gcc program.c -o output
  1. Tutaj pierwsza część przechowuje dane wyjściowe asemblera programu pod tą samą nazwą pliku, co Program, ale ze zmienionym rozszerzeniem .s , można go otworzyć jak każdy zwykły plik tekstowy.

  2. Druga część tutaj kompiluje twój program do faktycznego użycia i generuje plik wykonywalny dla twojego programu o określonej nazwie pliku.

Program.c użyte powyżej to nazwa programu i wyjście to nazwa pliku wykonywalnego chcesz wygenerować.

BTW To mój pierwszy post na StackOverFlow: -}

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.