nm wypisuje symbole w plikach wykonywalnych, plikach wynikowych (*.o) lub ich kolekcjach (bibliotekach, *.a). Składnia: nm plik. Plik wynikowy, z którego pochodzi symbol. Wartość symbolu. Typ symbolu (najczęściej spotykane): T w sekcji kodu (na ogół funkcja). U niezdefiniowany (w tym pliku). W symbol słaby. R w sekcji danych do odczytu. D w sekcji danych zainicjowanych. B w sekcji danych niezainicjowanych (BSS). małe litery symbole lokalne. wielkie globalne (zewnętrzne). 33
Przykład działania nm na prostym programie w C (po skompilowaniu, oczywiście). Po co nam nm? Przydaje się, gdy podczas kompilacji programu dostajemy błędy fazy linkowania np. niezdefiniowane symbole. Możemy wówczas poszukiwać jakiej biblioteki lub jakiego pliku wynikowego zapomnieliśmy włączyć, przeszukując symbole w bibliotekach. 34
Interesujące opcje nm: -C odmaglowuje (demangle) nazwy. -l pokazuje numery linii w plikach źródłowych, z których symbole pochodzą (konieczne -g przy kompilacji). Przykład: Kompilowane źródło Wydruk nm bez -C (nazwy przemaglowane). Wydruk nm -C (nazwy odmaglowane). Wydruk nm -Cl (nr linii, odmaglowanie). 35
make: inteligentna pomoc w rekompilacji programów. Ręczne kompilowanie programów jest nietrudne i OK, dopóki mamy niewiele plików źródłowych. Gdy plików zbierze się wiecej... Nieporęczne polecenie kompilacji. Nie warto przekompilowywać wszystkich plików, jeśli właśnie zmieniliśmy jeden. Ale skąd wiadomo, co trzeba przekompilować po wprowadzeniu naszej zmiany? Czasem nie tylko ten jeden, zmieniany plik. 36
Z pomocą przychodzi make sposób na poinstruowanie kompilatora jak należy przekompilować projekt. Przykład: mamy cztery pliki p1.cpp, p2.cpp, p3.c i main.cpp, które chcemy, by były skompilowane do p1.o, p2.o, p3.o, main.o, które następnie chcemy połączyć i dodając bibliotekę libbibl.a i otrzymać program wynikowy prog.out. "Ręcznie" postępowalibyśmy następująco: g++ -c p1.cpp p2.cpp main.cpp gcc -c p3.c g++ p1.o p2.o p3.o main.o -lbibl -o prog.out Zobaczmy, jak możemy opisać nasz projekt w języku make. Opis ten zapisujemy w pliku o nazwie Makefile lub makefile. wówczas wystarczy wydać polecenie make, żeby stworzyć program wynikowy, przekompilowując co potrzeba. 37
W naszym przypadku Makefile mógłby wyglądać następująco: Linia komentarza. Linie postaci "a : b" mówią "aby utworzyć a potrzebne jest b, oto przepis jak na podstawie b utworzyć a". Aby utworzyć prog.out potrzebne są p1.o, p2.o, p3.o, main.o, a przepis jest linię niżej. Aby utworzyć p1.o potrzebne jest p1.cpp, a przepis jest linię niżej. UWAGA: Linie zawierające "przepis" muszą zaczynać się od tabulatora (mcedit podświetla go na czerwono) inaczej make nie zrozumie naszych intencji. 38
W naszym przypadku Makefile mógłby wyglądać następująco: Przepis na "all" mówi co trzeba zrobić, aby utworzyć cały projekt. W naszym przypadku wystarczy powiedzieć, że "aby otrzymać all, potrzeba prog.out". Zwyczajowo dodaje się przepis na "clean", który mówi co zrobić, żeby wyczyścić projekt. make uruchomione bez parametrów wymusza przetwarzanie przepisów po kolei, co często jest równoważne "make all", czyli stara się utworzyć cały projekt. 39
Krótsza wersja, korzystająca z reguł wnioskowania (inference rules). Reguły wnioskowania mówią jak otrzymać wszystkie pliki jednego typu z plików drugiego typu, np. "Aby otrzymać potrzebne pliki *.o z plików *.cpp, zastosuj następujący przepis". Reguła wnioskowania "jak z *.cpp dostać *.o" Reguła wnioskowania "jak z *.c dostać *.o" Specjalne makra: $< plik przetwarzany przez regułę wnioskowania ("źródłowy"). $@ plik będący wynikiem przetwarzania przez regułę wnioskowania. 40
Wygodniej jest korzystać ze zmiennych. Teraz, gdy nasz program rozbuduje się o nowy plik, dopisujemy go do dwóch pierwszych linijek i po sprawie. Gdy chcemy przy kompilacji uwzględnić dodatkowe opcje (np. -g) możemy dopisać je do trzeciej linii. Podobnie łatwo radzimy ṡobie ze zmianą nazwy programu. Ważna zaleta make przekompilowane będą tylko te pliki, które trzeba make decyduje o tym na podstawie porównania czasów modyfikacji plików źródłowego i wynikowego. 41