C/C++ System Development Reference: Debugging, Libraries, and Build Tools

Locating Segmentation Faults with GDB and Core Dumps

Ensure the source code is compiled with the -g flag to embed debugging symbols.

  1. Check core file size limit: Run ulimit -c. A return value of 0 means core dumps are disabled; unlimited means no size restriction. Use ulimit -a to view all resource limits.
  2. Enable unlimited core size: ulimit -c unlimited
  3. Check core file path: cat /proc/sys/kernel/core_pattern
  4. Modify core file path: echo /tmp/coredump-%e-%s-%u-%g-%p-%t > /proc/sys/kernel/core_pattern
    • > redirects standard output to a file (overwrite).
    • < uses file content as standard input.
    • >> appends standard output to a file.
    • 2> redirects standard error to a file.
    • 2>&1 merges standard error into standard output.
  5. Analyze core file: gdb <executable_path> <core_file_path> (e.g., gdb ./my_app /tmp/coredump-my_app-11-0-0-2215-1706064035)
  6. View call stack: Type bt or backtrace in GDB.

GDB Debugging Session Commands

Always compile with the -g flag for debugging symbols.

Starting GDB

  • gdb <executable>: Launch GDB and load the binary.
  • gdb --args <executable> <arg1> <arg2>: Launch GDB and pass command-line arguments to the binary.

Breakpoints

break main         # Set breakpoint at function entry
break 42           # Set breakpoint at line 42 in current file
break app.c:42     # Set breakpoint at line 42 in app.c
break app.c:main   # Set breakpoint at main in app.c

Execution Control

run (r)            # Start program execution
run arg1 arg2      # Start execution with arguments
continue (c)       # Continue execution from current breakpoint
next (n)           # Step over (execute next line, do not enter functions)
step (s)           # Step into (execute next line, enter functions)

Inspecting Variables and Stack

print var_name     # Print variable value
info locals        # Display local variables in current frame
backtrace (bt)     # Display call stack
frame 2            # Switch to stack frame 2
up / down          # Navigate up/down the call stack

Memory Inspection

x/<format> <addr>  # Examine memory. e.g., x/x 0x400800 shows hex content

Debugging Information

list (l)           # Display source code around current line
info breakpoints   # Show current breakpoints
info threads       # Show current threads
info registers     # Show CPU registers

Altering Program State

  • set var_name = value: Modify a variable's value at runtime.
  • return expr: Force the current function to return the specified expression.

Exiting

quit (q)           # Terminate GDB

Note: If the call stack is corrupted and bt provides no useful information, insert debug printf statements to narrow down the crash location, paying close attention to pointer arithmetic and array boundaries.

Static (.a) vs Dynamic (.so) Libraries

Source Files

/* main.c */
#include <stdio.h>
#include "math_ops.h"

int main() {
    int x = 10, y = 4;
    int result = add_values(x, y);
    printf("Calculation Result: %d\n", result);
    return 0;
}

/* math_ops.h */
#ifndef MATH_OPS_H
#define MATH_OPS_H
int add_values(int a, int b);
#endif

/* math_ops.c */
#include "math_ops.h"

int add_values(int a, int b) {
    return a + b;
}

Creating and Using Static Libraries

  1. Compile the source into an object file: gcc -c math_ops.c -o math_ops.o
  2. Archive the object file into a static library using ar: ar rcs libmath_ops.a math_ops.o
  3. Link the main program against the static library (-L specifies the library path, -l specifies the library name omitting lib and .a): gcc main.c -L. -lmath_ops -o main_static
  4. Execute: ./main_static

Static libraries are linked at compile time and bundled into the final executable, increasing its file size.

Creating and Using Dynamic Libraries

  1. Compile into a shared library (-shared creates a shared object, -fPIC generates Position Independent Code): gcc -shared -fPIC -o libmath_ops.so math_ops.c
  2. Verify exported symbols using nm: nm libmath_ops.so
  3. Link the main program: gcc main.c -L. -lmath_ops -o main_dynamic
  4. Execute: ./main_dynamic
    If you encounter error while loading shared libraries: libmath_ops.so: cannot open shared object file, it means the runtime linker cannot find the library.
  5. Resolve by updating the library search path: export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

Dynamic libraries are linked at runtime, keeping the executable small, but they require the library to be present on the target system.

ARM Assembly Basics

  • Load (Read Memory): LDR R0, [R1, #4] — Reads 4 bytes from address R1 + 4 into R0.
  • Store (Write Memory): STR R0, [R1, #4] — Writes 4 bytes from R0 to address R1 + 4.
  • Arithmetic:
    • ADD R0, R1, R2R0 = R1 + R2
    • ADD R0, R0, #1R0 = R0 + 1
    • SUB R0, R1, R2R0 = R1 - R2
    • SUB R0, R0, #1R0 = R0 - 1
  • Compare: CMP R0, R1 — Subtracts R1 from R0 and updates the Program Status Register (PSR) flags.
  • Branching:
    • B label — Unconditional jump to label.
    • BL label — Branch with Link; stores the return address in the Link Register (LR) before jumping.

Makefile Essentials

Syntax and Execution

target: prerequisites
	recipe

A recipe executes if a prerequisite does not exist, or if a prerequisite's modification timestamp is newer than the target's timestamp.

Automatic Variables

  • $@: The target file.
  • $^: All prerequisite files.
  • $<: The first prerequisite file.

Phony Targets

.PHONY: clean ensures make clean runs even if a file named clean exists.

Variable Assignment

  • IMM := val: Immediate assignment; value is determined at definition.
  • DEL = val: Deferred assignment; value is determined when the variable is used.
  • ?= val: Conditional assignment; only sets the value if the variable is not already defined.
  • += val: Append; behaves as immediate or deferred based on the variable's initial definition.

Common Functions

  • $(foreach var, list, text): Iterates over list, replacing var in text.
  • $(filter pattern, text): Returns items in text matching pattern.
  • $(filter-out pattern, text): Returns items in text not matching pattern.
  • $(wildcard pattern): Returns existing files matching pattern.
  • $(patsubst pattern, replacement, text): Replaces pattern with replacement in text.

Dependency Generation

  • gcc -M src.c: Print all dependencies to stdout.
  • gcc -M -MF src.d src.c: Write dependencies to src.d.
  • gcc -c -o src.o src.c -MD -MF src.d: Compile and generate dependencies simultaneously.

Files starting with a dot (e.g., .src.o.d) are hidden in Linux.

Passing macros: EXTRA_CFLAGS := -DDEBUG

Makefile Example

OBJS = core.o app.o
DEP_FILES := $(patsubst %, %.d, $(OBJS))
DEP_FILES := $(wildcard $(DEP_FILES))

my_program: $(OBJS)
	gcc -o $@ $^
	@echo $(DEP_FILES)

core.o: core.c core.h

%.o: %.c
	gcc -c -o $@ $< -MD -MF .$@.d

ifneq ($(DEP_FILES), )
include $(DEP_FILES)
endif

clean:
	rm -f *.o my_program

C/C++ Concepts

Struct Assignment

Direct assignment of structs is valid in C/C++. The compiler handles member-wise copying.

struct Record {
    int id;
    int score;
};

struct Record r1;
struct Record r2 = {101, 95};
r1 = r2; // Valid

Returning Pointers to Local Variables

Returning a pointer to a local variable leads to undefined behavior (often a segmentation fault) because the stack frame is destroyed when the function returns.

int* generate_array() {
    int arr[5] = {1, 2, 3, 4, 5};
    return arr; // ERROR: Returning local stack memory
}

int main() {
    int* ptr = generate_array();
    // Accessing ptr[0] causes a segfault
    return 0;
}

Data Sizes and Bandwidth

  • 8 bits = 1 Byte
  • 1024 Bytes = 1 KB
  • 1024 KB = 1 MB
  • 1024 MB = 1 GB

Bandwidth conversion: 300 Mbps (Megabits per second) = 300 / 8 = 37.5 MB/s (Megabytes per second).

YUV formats:
YUV 4:2:0: Full Y sampling, half U sampling, quarter V sampling. Size = 1.5 bytes per pixel.
YUV 4:4:4: Full sampling for all components. Size = 3 bytes per pixel.
A 1080p frame (1920x1080) at YUV4:4:4 is approximately 6.2 MB. At 30 FPS, the data rate is roughly 186 MB/s (~0.18 GB/s).

Networking Basics

Local Area Network (LAN) devices access the internet through a router. The router possesses the public IP address. When a LAN device sends a request to an external server, the router maps the internal IP to its public IP and an assigned port (NAT). Two devices on separate LANs cannot communicate directly; they require a relay server or NAT traversal techniques (like STUN) to establish a connection.

MQTT Client Workflow

  1. Establish connection with the MQTT broker.
  2. Subscribe to desired topics.
  3. Enter main loop:
    • Read incoming packets: read_mqtt_packet(&buffer, len, timeout)
    • Process the received packet.
    • Send periodic Ping requests to keep the connection alive.

Running LVGL on Ubuntu

To validate LVGL (Light and Versatile Graphics Library) natively on Linux:

# Install dependencies
sudo apt update
sudo apt install build-essential git cmake xorg-dev libglu1-mesa-dev

# Clone the repository
git clone https://github.com/lvgl/lvgl.git
cd lvgl

# Build using CMake
mkdir build && cd build
cmake ..
make

# Run the benchmark example
cd ../lv_examples/lv_demo_benchmark
./lv_demo_benchmark

Protocol Buffers Implementation

Define the message structure in a .proto file:

syntax = "proto3";

message Employee {
    int32 emp_id = 1;
    string full_name = 2;
    string department = 3;
}

Use the protoc compiler to generate C++ headers and sources (e.g., employee.pb.h, employee.pb.cc).

Sender: Serialization

#include <fstream>
#include "employee.pb.h"

int main() {
    Employee emp;
    emp.set_emp_id(42);
    emp.set_full_name("Jane Doe");
    emp.set_department("Engineering");

    std::string serialized;
    emp.SerializeToString(&serialized);

    std::ofstream out("emp_data.bin", std::ios::binary);
    out.write(serialized.data(), serialized.size());
    out.close();
    return 0;
}

Receiver: Deserialization

#include <iostream>
#include <fstream>
#include "employee.pb.h"

int main() {
    std::ifstream in("emp_data.bin", std::ios::binary);
    std::string serialized((std::istreambuf_iterator<char>(in)), 
                           std::istreambuf_iterator<char>());
    in.close();

    Employee emp;
    emp.ParseFromString(serialized);

    std::cout << "ID: " << emp.emp_id() << "\n"
              << "Name: " << emp.full_name() << "\n"
              << "Dept: " << emp.department() << std::endl;
    return 0;
}

Tags: gdb C++ Makefile Static Libraries Dynamic Libraries

Posted on Fri, 29 May 2026 19:10:11 +0000 by globalinsites