Level 2 — RecallBuild Systems & Toolchain

Build Systems & Toolchain

30 minutes40 marksprintable — key stays hidden on paper

Chapter: 5.3 Build Systems & Toolchain Level: 2 — Recall (definitions, standard problems, short derivations) Time Limit: 30 minutes Total Marks: 40


Instructions: Answer all questions. Marks are shown against each question.


Q1. List the four stages of compilation in order, and state briefly what each stage produces. (4 marks)

Q2. Match each file extension to its correct description: (4 marks)

Extension
(a) .o
(b) .a
(c) .so
(d) .i (or preprocessed output)

Descriptions: (i) static library archive, (ii) shared/dynamic library, (iii) relocatable object file, (iv) preprocessed source.

Q3. Define the following two components of an object file: (4 marks) (a) the symbol table (b) a relocation entry

Q4. State one advantage and one disadvantage of using a dynamic/shared library instead of a static library. What does the abbreviation PIC stand for and why is it needed for shared libraries? (5 marks)

Q5. Consider the link command below, where libutil.a defines func() and main.o calls func(): (4 marks)

gcc libutil.a main.o -o prog

This command fails with an "undefined reference to func" error. Explain why, and give the corrected command.

Q6. For the Makefile rule below, name the automatic variables $@, $<, and $^, and give their values for this rule. (6 marks)

prog: main.o util.o
	gcc $^ -o $@

Q7. (4 marks) (a) What is a phony target in a Makefile, and how do you declare one? (b) Write a phony clean target that removes *.o and prog.

Q8. State what each GCC/Clang flag does: (5 marks) (a) -O0 (b) -O2 (c) -Wall (d) -g (e) -fsanitize=address

Q9. Name the appropriate sanitizer or tool for each purpose: (4 marks) (a) detecting heap buffer overflows at runtime (b) detecting data races between threads (c) detecting signed integer overflow / undefined behavior (d) disassembling an object file to read its assembly

Answer keyMark scheme & solutions

Q1. (4 marks — 1 each)

  1. Preprocessing — expands #include, macros, conditionals → produces preprocessed source (.i). (1)
  2. Compilation — translates C/C++ into assembly → produces assembly (.s). (1)
  3. Assembly — assembler converts assembly to machine code → produces object file (.o). (1)
  4. Linking — combines object files and libraries, resolves symbols → produces executable. (1)

Q2. (4 marks — 1 each) (a) .o → (iii) relocatable object file (b) .a → (i) static library archive (c) .so → (ii) shared/dynamic library (d) .i → (iv) preprocessed source


Q3. (4 marks) (a) Symbol table — a list of symbols (functions/variables) defined and referenced by the object, recording their names, whether they are defined here or external, and their section/offset. (2) (b) Relocation entry — a record telling the linker which locations in the code/data must be patched with final addresses once symbols are placed, i.e. it fixes up references whose addresses aren't known until link time. (2)


Q4. (5 marks)

  • Advantage (any one): smaller executables; library shared in memory across processes; can update the library without relinking. (1.5)
  • Disadvantage (any one): runtime dependency (must be present/found at load time); slight load-time/indirection overhead; version/"DLL hell" issues. (1.5)
  • PIC = Position-Independent Code. (1)
  • Needed because a shared library may be loaded at any address in different processes; PIC uses relative addressing (e.g. via GOT/PLT) so the code works regardless of load address without per-load relocation of the code segment. (1)

Q5. (4 marks)

  • Why: Linkers process inputs left to right; when libutil.a is seen first, no unresolved symbols exist yet, so the linker pulls nothing from the archive. Then main.o introduces a reference to func, but the archive has already been passed → undefined reference. Order matters: libraries must come after the objects that use them. (2)
  • Corrected command: (2)
gcc main.o libutil.a -o prog

Q6. (6 marks — 1 for name + 1 for value each)

  • $@ = the target name → value prog. (2)
  • $< = the first prerequisite → value main.o. (2)
  • $^ = all prerequisites (deduplicated) → value main.o util.o. (2)

Q7. (4 marks) (a) A phony target is one that does not represent an actual file; declared with .PHONY: so make always runs its recipe (won't be skipped even if a file of that name exists). (2) (b) (2)

.PHONY: clean
clean:
	rm -f *.o prog

Q8. (5 marks — 1 each) (a) -O0 — no optimization (default, fastest compile, best for debugging). (b) -O2 — enable a broad set of optimizations for speed without excessive code size. (c) -Wall — enable common/recommended warning messages. (d) -g — emit debug information (symbols, line numbers) for use by GDB. (e) -fsanitize=address — instrument with AddressSanitizer to detect memory errors (buffer overflows, use-after-free) at runtime.


Q9. (4 marks — 1 each) (a) AddressSanitizer (ASan)-fsanitize=address (b) ThreadSanitizer (TSan)-fsanitize=thread (c) UndefinedBehaviorSanitizer (UBSan)-fsanitize=undefined (d) objdump (e.g. objdump -d)


[
  {"claim":"There are exactly 4 classic compilation stages listed in Q1","code":"stages=['preprocess','compile','assemble','link']; result = (len(stages)==4)"},
  {"claim":"Q6 automatic variable values: $@=prog, $<=main.o, $^ joins all prereqs","code":"target='prog'; prereqs=['main.o','util.o']; at=target; first=prereqs[0]; allp=' '.join(prereqs); result = (at=='prog' and first=='main.o' and allp=='main.o util.o')"},
  {"claim":"Correct link order places objects before archives (main.o then libutil.a)","code":"order=['main.o','libutil.a']; result = order.index('main.o') < order.index('libutil.a')"},
  {"claim":"Q8 has 5 flags mapped one-to-one","code":"flags={'-O0','-O2','-Wall','-g','-fsanitize=address'}; result = (len(flags)==5)"}
]