Introduktion till kompilatorn gcc



Introduktion till kompilatorn gcc

Introduktion till kompilatorn gcc



Kort introduktion till gcc

gcc är en kompilatorsvit som består av en bakända och en frontända.

Bakändan skapar körbar kod för en viss processor och ett visst
operativsystem. Frontändan kompilerar kod för olika programspråk,
t.ex. c och c++. Man har med andra ord skapat ett antal frontändar
för de olika språken och ett antal bakändar för de olika
plattformarna.

Kompilator gcc följer normalt ANSI-standard men det går att få den att
följa gammal c-standard.

När man kör gcc eller g++ så går den igenom alla steg från att
köra preprocessorn, kompilera koden, assemblera koden och länka samman
all kod till ett program. Detta gör den genom att starta olika
delprogram som ingår i gcc-sviten.

Preprocessorn för c, c++ och objective-c heter cpp. Kompilatorn för c heter gcc
Kompilatorn för c++ heter g++, kompilatorn för Ada heter gnat.
Assemblatorn heter as. Standardlänkaren heter ld. Det finns även andra länkare som heter bfd respektive gold.
Preprocessor och kompilator/kompilatorer ingår i gcc medans
assemblator, länkare och verktyg för att titta på och ändra på objektfiler
ingår i binutils (exempel: as, ld, bfd, gold, objcopy, objdump, readelf, nm, strip, ar etc).

Viktiga flaggor till gcc
Om inga flaggor ges skrivs resultatet till en programfil som får
namnet a.out. Om man vill att resultatet ska skrivas till en
programfil med ett annat namn får man ge flaggan -o följt av filnamn.
Exempel:
Kod: Markera allt
gcc fil.c -o programfil


För att få binärkoden mer optimerad ska man ge flaggan -O och en
siffra t.ex. -O2. Exempel:
Kod: Markera allt
gcc fil.c -O2 -o programfil


För att bara kompilera till objektfiler, .o-filer, ska man ge flaggan
-c. Exempel:
Kod: Markera allt
gcc -c fil1.c fil2.c

Raden ovan gör att fil1.c kompileras till fil1.o och fil2.c till fil2.o.


Om man vill länka med ett statiskt bibliotek så görs detta med flaggan -l
(lilla L). Exempel för att länka med mattebiblioteket som finns i filen
libm.a:
Kod: Markera allt
gcc program.c -lm -o programfil

Om filen med det statiska biblioteket heter libfoo.a så ska man skala
bort lib och .a och bara ge foo till flaggan -l. D.v.s. -lfoo .

För att länka med dynamiska bibliotek (shared libraries) används också
flaggan -l. T.ex. för att länka mot libfoo.so.1 lägger man till -lfoo . Om det finns både ett statiskt och ett dynamiskt bibliotek så väljer länkaren det dynamiska biblioteket (shared library).

Det går även att ge sökvägen och filnamn till ett bibliotek till
länkaren. exempel:
Kod: Markera allt
gcc program.c /usr/lib/libm.a -o programfil



Observera att alla källkodsfiler måste ges före länkdirektiven på
kommandoraden. Ett korrekt exempel:
Kod: Markera allt
gcc fil1.c fil2.c fil3.c -lm -lfoo -lbar -o programmet


Ett felaktigt och inkorrekt exempel:
Kod: Markera allt
gcc -lm -lfoo -lbar fil1.c fil2.c fil3.c -o programmet

Länkdirektiven skickar gcc vidare till länkaren.


Om man vill att gcc ska söka efter libfiler i en speciell katalog kan
det göras genom att ge flaggan -L sökväg/till/katalog . Exempel:
Kod: Markera allt
gcc fil1.c fil2.c -L libkatalogen  -lfoo -o program




Man kan ange en specifik major-version av ett lib med -l:<libfil>.so.<MajorVersion> Exempel:
Kod: Markera allt
gcc fil1.c fil2.c -L libkatalogen  -l:foo.so.1 -o program




För att få gcc att söka efter include-filer inte bara i katalogen man
står i utan även någon speciell katalog ger man flaggan -I följt av
sökväg till katalogen. Exempel:
Kod: Markera allt
gcc fil1.c fil2.c -I includekatalog -o program



Om man vill att kompilatorn ska ge mer information och fler varningar
kan man lägga till -Wall. Om man dessutom vill att den strikt ska
följa ANSI c90 standard och klaga om standarden inte följs kan man ge flaggan
-ansi. För att göra kompilatorn extra pedantiskt och petig lägger man
till -pedantic. Exempel:
Kod: Markera allt
gcc fil1.c fil2.c -Wall -ansi -pedantic -o program

Om kompileringen med -Wall -ansi och -pedantic inte ger några fel
eller varning så är koden syntaktiskt bra.

Om man istället vill att kompilatorn ska följa C99 standard ska man till gcc inte använda -ansi utan istället ange -std=c99. För nyare standarder anger man -std=c11 för C11-standard, -std=c17 för c17-standard. För C++ finns motsvarigheten för bland annat standarderna c++11, c++14, c++17 som anges till -std=. Se manualen till gcc för stödet för fler standarder. Exempel:
Kod: Markera allt
gcc -std=c11 program.c -o program

Kod: Markera allt
g++ -std=c++17 program.cpp -o program



I programkoden kan man definiera saker med #define. Dessa går även att
definiera på kommandoraden med -D. T.ex. #define DEBUG blir -DDEBUG .
Macron som ges värden kan på kommandoraden skrivas som -Dmacro=värde.




Ett större exempel på kompilering av flera filer och länkning med flera
libfiler till en programfil:
Kod: Markera allt
gcc -Wall -ansi -pedantic fil1.c fil2.c fil3.c -I includekatalog -L libkatalog -lfoo -lbar -o program


>Debugging och profilering
För att få med debuginformation för att underlätta debuggande av koden
ska man ge flaggan -g. I och med det kan man använda t.ex. gdb för att
debugga programmet. Exempel:
Kod: Markera allt
gcc -g fil1.c fil2.c -o program

Nu går det att köra gdb på program.
Kod: Markera allt
gdb program


För att även få med profileringsinformation ska man ge flaggan
-pg. Med profilering går det att studera:
* vilka funktioner som anropar vilka andra funktioner.
* hur många gånger som olika funktioner anropas.
* hur mycket körtid som som används i de olika delarna av programmet.

När programmet körs skapas filen gmon.out som innehåller
profileringsinformationen. För att studera profileringsinformationen
kör man därefter gprof på programmet där gprof även läser i gmon.out.
Exempel:
Kod: Markera allt
gcc -pg filen.c -o programmet
./programmet
gprof programmet | less




Lite mer om länkaren
När man anger -l eller -L till gcc/g++ så skickas det vidare till länkaren och används i länkningssteget. Det finns många andra flaggor och argument som man kan ge till länkaren. Här tänker jag bara ta upp lite av vad man kan göra.

Om man vill tvinga länkaren att länka ett program statiskt istället för dynamiskt, som annars är standard (default), kan man göra det genom att ange -static till länkaren. Exempel:
Kod: Markera allt
gcc fil1.c fil2.c -lfoo -static -o program


Om man har bibliotek som ligger på icke standardplatser kommer biblioteksladdaren vanligtvis inte att kunna hitta dessa bibliotek när man försöker köra ett dynamiskt länkat program med referenser till bibliotek som finns där. Om du t.ex. har ett dynamiskt bibliotek libfoo.so.1 i /opt/foo/lib kommer biblioteksladdaren, ld.so, att inte hitta den.

Det finns flera olika sätt att lösa det på.

Ett sätt är att skapa en fil i katalogen /etc/ld.so.conf.d/ och i den filen lägga in sökvägen till katalogen där bilioteksfilerna ligger. Filen ska ha ett namn som slutar på .conf. Exempel:
Kod: Markera allt
/etc/ld.so.conf.d/optfoolib.conf

Med innehållet:
Kod: Markera allt
/opt/foo/lib


Därefter behöver du som användaren root köra programmet ldconfig. Den kommer att gå igenom konfigurationsfilerna och skapa filen /etc/ld.so.cache som är den filen som används av ld.so.



Ett annat alternativ är att du i ditt kommandoskal sätter variabeln LD_LIBRARY_PATH till katalogen i vilken biblioteksfilen/filerna ligger och därefter startar programmet. Exempel:
Kod: Markera allt
export LD_LIBRARY_PATH=/opt/foo/lib


Det går också att sätta rpath i programbinären där rpath ska innehålla sökvägen eller sökvägarna till det eller de bibliotek som ska hittas. För att sätta rpath måste man tala om för länkaren i samband med kompilering att den ska sätta rpath vilket görs med -Wl,-rpath,/path/to/lib till gcc. Exempel:
Kod: Markera allt
gcc fil1.c fil2.c -L /opt/foo/lib  -lfoo -Wl,-rpath,/opt/foo/lib -o program


Om man har flera sökvägar som ska in i rpath kan det göras på bland annat följande två sätt.
1) att ange -rpath och flera sökvägar separerade med kolon.
Kod: Markera allt
gcc fil1.c fil2.c -L /opt/foo/lib  -lfoo -L /opt/bar/lib -lbar -Wl,-rpath,/opt/foo/lib:/opt/bar/lib -o program


2) Ange en -rpath per sökväg till bibliotekskatalog.
Kod: Markera allt
gcc fil1.c fil2.c -L /opt/foo/lib  -lfoo -L /opt/bar/lib -lbar
-Wl,-rpath,/opt/foo/lib,-rpath,/opt/bar/lib  -o program


Om biblioteket ligger i en katalog i anslutning till den slutgiltiga binären kan man använda $ORIGIN som under runtime kommer att epxanderas till katalogen för programbinären. Observera att du behöver ha enkelfnuttar ' runt $ORIGIN för att förhindra att kommandoskalet expanderar $ORIGIN vid kompilering. Exempel:
Kod: Markera allt
gcc fil1.c fil2.c -L /opt/foo/lib  -lfoo '-Wl,-rpath,$ORIGIN/../lib' -o /opt/foo/bin/program


Om man vill använda någon annan länkare än den som är standard för gcc, d.v.s. man vill vill använda någon annan länkare än ld så kan det göras med flaggan -fuse-ld=annanlänkare. Länkare kan vara t.ex. någon av de som finns i binutils eller t.ex. lld från kompilatorsviten LLVM.

Kod: Markera allt
-fuse-ld=bfd



Kod: Markera allt
-fuse-ld=gold



Kod: Markera allt
-fuse-ld=lld







Studera färdigkompilerade ELF-binärer
Program som kompileras till Linux har ett binärformat som heter ELF. ELF står för Executable and Linkable Format. I början av ett program finns en header med en hel del information som är relevant för programmet. För att studera en ELF-binär och t.ex. studera delar av denna header finns ett ental verktyg till hjälp.

Till att börja med kan man vilja ta reda på att det är en ELF-binär, vilken arkitektur den är byggd för, vilket operativsystem (OS) som den är byggd för och om den är statiskt eller dynamiskt länkad. För detta ändamål finns programmet file.
Kod: Markera allt
kjell-e@roadrunner:~ $ file program
a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=af67ba2e624801648107a440bb40f787d0ae3e1e, not stripped


Nästa steg kan vara att lista de dynamisk bibliotek som programmet är beroende av. Det går att göra med programmet ldd
Kod: Markera allt
kjell-e@roadrunner:~ $ ldd program
linux-vdso.so.1 (0x00007ffef5bfa000)
libfoo.so.1 => /opt/foo/lib1/libfoo.so.1 (0x00007ff09b8a1000)
libbar.so.1 => /opt/foo/lib2/libbar.so.1 (0x00007ff09b69f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff09b2ae000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff09bca5000)

Här kan vi se att programmet är beroende av linux-vdso.so.1, libfoo.so.1, libbar.so.1 , libc.so.6 och ld-linux-x86-64.so.2. Referensen linux-vdso.so.1 är ett virtuellt dynamiskt bibliotek där den är en del av Linuxkärnan. Denna ligger inte lagrad i något filsystem. libfoo.so.1 och libbar.so.1 är två bibliotek med någon funktionalitet som används av programmet. libc.so.6 är standard libc, d.v.s. den innehåller alla standardfunktioner som behövs. ld-linux-x86-64.so.2 är den dynamiska biblioteksladdaren. Det är ld-linux-x86-64.so.2 som ansvarar för att leta reda på och ladda in de dynamiska bibliotek som behövs av programmet när programmet startar.


Med programmet readelf kan man läsa ut information om en ELF-binär. Ett exempel kan vara att lista bibliotek som den är beroende av och lista rpath som här visas som RUNPATH. Programmet readelf kan visa alla delar av ELF-headern till ett program. I exemplet nedan listas programmets dynamiska sektion:
Kod: Markera allt
kjell-e@roadrunner:~ $ readelf -d program
Dynamic section at offset 0xd80 contains 30 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libfoo.so.1]
0x0000000000000001 (NEEDED) Shared library: [libbar.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN/foo/:$ORIGIN/bar/]
...
...
...
0x0000000000000000 (NULL) 0x0


Två andra användbara program är objdump och objcopy. Programmet objdump visar information om en fil t.ex. ett program. Programmet objcopy kopierar en fil och kan manipulera och ändra i det den kopierar. Se mansidan för mer information. Nedan visas ett exempel på objdump -x program, där den visar all header-information. Endast delar av headern är med i exemplet:
Kod: Markera allt
kjell-e@roadrunner:~ $ objdump -x program
program: file format elf64-x86-64
program
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x0000000000000700

Program Header:
    PHDR off 0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3
         filesz 0x00000000000001f8 memsz 0x00000000000001f8 flags r--
...
...
...
Dynamic Section:
  NEEDED     libfoo.so.1
  NEEDED     libbar.so.1
  NEEDED     libc.so.6
  RUNPATH     $ORIGIN/lib1:$ORIGIN/lib2
  INIT     0x0000000000000688
...
...
...
0000000000000000 w F *UND* 0000000000000000 __cxa_finalize@@GLIBC_2.2.5
0000000000000688 g F .init 0000000000000000 _init




En omfattande bok som tar upp det mesta av det man behöver veta om kompilering och biblioteksfiler är boken
Advanced C and C++ Compiling av Milan Stevanovic.


En annan mycket bra bok om man ska hålla på med systemprogrammering i Linux är
The Linux Programming Interface av Michael Kerrisk.
Användarvisningsbild
kjell-e
 



Copyright © 2010-2024 Kjell Enblom.
This document is covered by the GNU Free Documentation License, Version 1.3

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".