First lets do some initial setup by creating the project folder and going inside:
mkdir gdb_stub
cd gdb_stub
Create a file named gdb_stub.c
you can do this from your favourite IDE or just use bash:
touch gdb_stub.c
Open gdb_stub.c
in your favorite text editor/IDE and add the following basic structure:
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("GDB Stub is starting...\n");
return 0;
}
Compile the code:
gcc -o gdb_stub gdb_stub.c
Run the executable:
./gdb_stub
You should see:
GDB Stub is starting...
Update your gdb_stub.c
to include networking libraries:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>
#define PORT 1234
int main() {
printf("GDB Stub is starting...\n");
return 0;
}
Add code to create a socket and bind it to the specified port:
int main() {
int server_sock;
struct sockaddr_in server_addr;
// Create a socket
if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Set up the server address
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// Bind the socket
if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_sock);
exit(EXIT_FAILURE);
}
printf("GDB Stub listening on port %d...\n", PORT);
close(server_sock); // Close socket for now
return 0;
}
Compile and run the program again:
gcc -o gdb_stub gdb_stub.c
./gdb_stub
You should see:
GDB Stub listening on port 1234...
Modify the main function to accept a client connection:
int main() {
int server_sock, client_sock;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
// Create a socket
if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Set up the server address
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// Bind the socket
if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_sock);
exit(EXIT_FAILURE);
}
// Listen for incoming connections
if (listen(server_sock, 1) < 0) {
perror("Listen failed");
close(server_sock);
exit(EXIT_FAILURE);
}
printf("GDB Stub listening on port %d...\n", PORT);
// Accept a client connection
if ((client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &addr_len)) < 0) {
perror("Accept failed");
close(server_sock);
exit(EXIT_FAILURE);
}
printf("GDB connected from %s\n", inet_ntoa(client_addr.sin_addr));
close(client_sock); // Close client socket for now
close(server_sock); // Close server socket
return 0;
}
Compile and run the program again:
gcc -o gdb_stub gdb_stub.c
./gdb_stub
(You’ll need to connect using GDB later to see the “GDB connected” message.)
Add functionality to receive packets from GDB:
char buffer[256]; // Buffer to store incoming data
// Inside the main function, after accepting the connection
ssize_t n = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
if (n < 0) {
perror("Receive failed");
} else {
buffer[n] = '\0'; // Null-terminate the received string
printf("Received packet: %s\n", buffer);
}
Update your gdb_stub.c
to include the new code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 1234
int main() {
int server_sock, client_sock;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
char buffer[256]; // Buffer to store incoming data
// Create a socket
if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Set up the server address
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// Bind the socket
if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_sock);
exit(EXIT_FAILURE);
}
// Listen for incoming connections
if (listen(server_sock, 1) < 0) {
perror("Listen failed");
close(server_sock);
exit(EXIT_FAILURE);
}
printf("GDB Stub listening on port %d...\n", PORT);
// Accept a client connection
if ((client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &addr_len)) < 0) {
perror("Accept failed");
close(server_sock);
exit(EXIT_FAILURE);
}
printf("GDB connected from %s\n", inet_ntoa(client_addr.sin_addr));
// Receive data from GDB
ssize_t n = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
if (n < 0) {
perror("Receive failed");
} else {
buffer[n] = '\0'; // Null-terminate the received string
printf("Received packet: %s\n", buffer);
}
close(client_sock); // Close client socket
close(server_sock); // Close server socket
return 0;
}
Compile and run the program again:
gcc -o gdb_stub gdb_stub.c
./gdb_stub
The main function is getting a bit large so lets create some new functions for the next part of the functionality, we won’t keep posting the full code for every step.
To reduce the code in the main function lets move all the code after the folloing line into its own function called handle_client :
printf("GDB connected from %s\n", inet_ntoa(client_addr.sin_addr));
After that line replace the contents with the following:
// Receive data from GDB client
handle_client(client_sock);
Now we need to create the handle_client function after the main function:
// Handle communication with the GDB client
void handle_client(int client_sock) {
char packet_buffer[4096];
uint8_t received_checksum;
while (1) {
// Receive packet from GDB
char *packet = recv_packet(client_sock, packet_buffer, sizeof(packet_buffer), &received_checksum);
if (!packet) {
printf("Connection closed or packet error.\n");
break;
}
printf("Received packet from GDB client: %s\n", packet);
send_packet(client_sock, ""); // Send blank data back
}
}
// Receive a packet from GDB
char *recv_packet(int client_sock, char *buffer, size_t buffer_size, uint8_t *received_checksum) {
ssize_t n;
char c;
size_t idx = 0;
int reading = 0;
while ((n = recv(client_sock, &c, 1, 0)) > 0) {
if (c == '$') {
// Start of a packet
reading = 1;
idx = 0;
} else if (reading && c == '#') {
// End of packet, read checksum
char checksum_str[3] = {0};
if (recv(client_sock, &checksum_str[0], 2, 0) != 2) {
return NULL; // Failed to read checksum
}
*received_checksum = (uint8_t)strtol(checksum_str, NULL, 16);
buffer[idx] = '\0'; // Null-terminate the packet
return buffer;
} else if (reading) {
if (idx < buffer_size - 1) {
buffer[idx++] = c;
} else {
// Buffer overflow, invalid packet
return NULL;
}
}
}
return NULL; // Connection closed or error
}
We can now create the function that sends data back to the GDB client like so:
// Send a packet to GDB
void send_packet(int client_sock, const char *data) {
char packet[4096];
uint8_t cksum = 0; // We will implement this in the next step: calculate_checksum(data);
// Format the packet: $<data>#<checksum>
snprintf(packet, sizeof(packet), "$%s#%02x", data, cksum);
// Send the packet
ssize_t sent = send(client_sock, packet, strlen(packet), 0);
if (sent < 0) {
perror("Failed to send packet");
} else {
// Debug: Print sent packet
printf("Sent packet: %s\n", packet);
}
}
Compile and run the program again:
gcc -o gdb_stub gdb_stub.c
./gdb_stub
But this time also open gdb in a new terminal like so:
gdb
Now in GDB you can connect to your stub like so:
(gdb) target remote localhost:1234
Go back to the terminal with your gdb_stub and you will notice GDB client will start sending you messages that will be logged for example:
GDB Stub listening on port 1234...
GDB connected from 127.0.0.1
Received packet from GDB client: qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;QThreadOptions+;no-resumed+;memory-tagging+;xmlRegisters=i386
We are making great progress!
The GDB Remote Protocol is fault tolerant which means we need to support message checksums, these make sure that the messages were all sent uncorrupted through TCP stack and we can send back acknowledgement so that the client knows we got the message successfully.
// Calculate checksum for GDB packet
uint8_t calculate_checksum(const char *data) {
uint8_t sum = 0;
while (*data) {
sum += (uint8_t)*data++;
}
return sum;
}
You can now fix the line in the send_packet function to actual calculate the checksum:
void send_packet(int client_sock, const char *data) {
char packet[4096];
uint8_t cksum = calculate_checksum(data);
...
// Send acknowledgment ('+' or '-')
void send_ack(int client_sock, char ack) {
char ack_packet[2] = {ack, '\0'};
ssize_t sent = send(client_sock, &ack_packet[0], 1, 0);
if (sent < 0) {
perror("Failed to send acknowledgment");
} else {
printf("Sent acknowledgment: %c\n", ack);
}
}
Lets now call the send_ack function inside the handle_client after we have checked that the checksum is correct:
printf("Received packet from GDB client: %s\n", packet); // This line was existing, new code is below it
// Calculate checksum
uint8_t calculated_checksum = calculate_checksum(packet);
if (calculated_checksum != received_checksum) {
printf("Checksum mismatch: calculated %02x, received %02x\n", calculated_checksum, received_checksum);
send_ack(client_sock, '-'); // Negative acknowledgment
continue;
} else {
send_ack(client_sock, '+'); // Positive acknowledgment
}
Compile and run the program again, along with starting gdb in the same way as the last step:
gcc -o gdb_stub gdb_stub.c && ./gdb_stub
You will now notice that we have the following output when the checksums match:
Sent acknowledgment: +
Now its time to actually parse and return useful information based on the GDB commands the client is sending us.
At the end of the handle_client function add the following code:
// Handle GDB commands
handle_gdb_command(client_sock, packet);
Here is a dummy example of how you can handle some of the commands, but this will need to be personalised to your specific emulator:
#define MEMORY_SIZE 1024 // Define the size of your memory array in your emulator
char memory_array[MEMORY_SIZE]; // Memory array for demonstration replace with your emulators memory
// Function to handle GDB commands
void handle_gdb_command(int client_sock, const char *packet) {
if (strcmp(packet, "g") == 0) {
// Return the general registers (dummy response)
send_packet(client_sock, "00000000"); // Example: 4 bytes for simplicity
} else if (packet[0] == 'm') {
// Memory read command, format: m<addr>,<length>
unsigned long addr, length;
if (sscanf(packet + 1, "%lx,%lx", &addr, &length) != 2) {
send_packet(client_sock, "E00"); // Invalid format
return;
}
if (addr + length > MEMORY_SIZE) {
send_packet(client_sock, "E01"); // Error: Out of bounds
return;
}
// Convert memory to hex string
char mem_data[length * 2 + 1];
for (unsigned long i = 0; i < length; i++) {
sprintf(mem_data + i * 2, "%02x", memory_array[addr + i]);
}
mem_data[length * 2] = '\0'; // Null-terminate
send_packet(client_sock, mem_data);
} else if (packet[0] == 'M') {
// Memory write command, format: M<addr>,<length>:<data>
unsigned long addr, length;
char data[4096]; // Buffer for the data
// Split the packet into address,length and data
char *colon = strchr(packet, ':');
if (!colon) {
send_packet(client_sock, "E00"); // Invalid format
return;
}
// Parse address and length
if (sscanf(packet + 1, "%lx,%lx", &addr, &length) != 2) {
send_packet(client_sock, "E00"); // Invalid format
return;
}
// Extract data
strcpy(data, colon + 1);
// Validate data length
if (strlen(data) != length * 2) {
send_packet(client_sock, "E00"); // Data length mismatch
return;
}
if (addr + length > MEMORY_SIZE) {
send_packet(client_sock, "E01"); // Error: Out of bounds
return;
}
// Convert hex data to binary and write to memory
for (unsigned long i = 0; i < length; i++) {
unsigned int byte;
if (sscanf(&data[i * 2], "%2x", &byte) != 1) {
send_packet(client_sock, "E00"); // Invalid hex
return;
}
memory_array[addr + i] = (uint8_t)byte;
}
// Send OK if successful
send_packet(client_sock, "OK");
} else if (strcmp(packet, "?") == 0) {
// Signal reporting (dummy response)
send_packet(client_sock, "S05"); // Example: Signal 5 (SIGTRAP)
} else if (strcmp(packet, "k") == 0) {
// Kill command, send OK and terminate
send_packet(client_sock, "OK");
} else if (packet[0] == 'q') {
// Handle queries
handle_query(client_sock, packet);
} else if (packet[0] == 'v') {
// Handle extended commands
handle_v_command(client_sock, packet);
} else {
// Unknown command, send empty response
send_packet(client_sock, "");
}
}
Also a few helped functions:
// Handle 'q' queries
void handle_query(int client_sock, const char *packet) {
if (strcmp(packet, "qSupported") == 0) {
// Respond with supported features
// Example: "PacketSize=4000"
send_packet(client_sock, "PacketSize=4000");
} else if (strncmp(packet, "qSupported+", 11) == 0) {
// Handle specific supported queries if needed
// For simplicity, respond with empty
send_packet(client_sock, "");
} else {
// Unknown query, respond with empty
send_packet(client_sock, "");
}
}
// Handle 'v' extended commands
void handle_v_command(int client_sock, const char *packet) {
if (strcmp(packet, "vMustReplyEmpty") == 0) {
// According to GDB RSP, reply with empty packet
send_packet(client_sock, "");
} else {
// Unknown 'v' command, respond with empty
send_packet(client_sock, "");
}
}
Lets put it all together to get the following:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <time.h>
#define PORT 1234
#define MEMORY_SIZE 1024
// Simulated memory
uint8_t memory_array[MEMORY_SIZE];
// Function prototypes
void handle_client(int client_sock);
void send_packet(int client_sock, const char *data);
char *recv_packet(int client_sock, char *buffer, size_t buffer_size, uint8_t *received_checksum);
uint8_t calculate_checksum(const char *data);
void send_ack(int client_sock, char ack);
void handle_query(int client_sock, const char *packet);
void handle_v_command(int client_sock, const char *packet);
int main() {
int server_sock, client_sock;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
char buffer[256]; // Buffer to store incoming data
// Create a socket
if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Set up the server address
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// Bind the socket
if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_sock);
exit(EXIT_FAILURE);
}
// Listen for incoming connections
if (listen(server_sock, 1) < 0) {
perror("Listen failed");
close(server_sock);
exit(EXIT_FAILURE);
}
printf("GDB Stub listening on port %d...\n", PORT);
// Accept a client connection
if ((client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &addr_len)) < 0) {
perror("Accept failed");
close(server_sock);
exit(EXIT_FAILURE);
}
printf("GDB connected from %s\n", inet_ntoa(client_addr.sin_addr));
// Receive data from GDB client
handle_client(client_sock);
close(client_sock); // Close client socket
close(server_sock); // Close server socket
return 0;
}
// Function to handle GDB commands
void handle_gdb_command(int client_sock, const char *packet) {
if (strcmp(packet, "g") == 0) {
// Return the general registers (dummy response)
send_packet(client_sock, "00000000"); // Example: 4 bytes for simplicity
} else if (packet[0] == 'm') {
// Memory read command, format: m<addr>,<length>
unsigned long addr, length;
if (sscanf(packet + 1, "%lx,%lx", &addr, &length) != 2) {
send_packet(client_sock, "E00"); // Invalid format
return;
}
if (addr + length > MEMORY_SIZE) {
send_packet(client_sock, "E01"); // Error: Out of bounds
return;
}
// Convert memory to hex string
char mem_data[length * 2 + 1];
for (unsigned long i = 0; i < length; i++) {
sprintf(mem_data + i * 2, "%02x", memory_array[addr + i]);
}
mem_data[length * 2] = '\0'; // Null-terminate
send_packet(client_sock, mem_data);
} else if (packet[0] == 'M') {
// Memory write command, format: M<addr>,<length>:<data>
unsigned long addr, length;
char data[4096]; // Buffer for the data
// Split the packet into address,length and data
char *colon = strchr(packet, ':');
if (!colon) {
send_packet(client_sock, "E00"); // Invalid format
return;
}
// Parse address and length
if (sscanf(packet + 1, "%lx,%lx", &addr, &length) != 2) {
send_packet(client_sock, "E00"); // Invalid format
return;
}
// Extract data
strcpy(data, colon + 1);
// Validate data length
if (strlen(data) != length * 2) {
send_packet(client_sock, "E00"); // Data length mismatch
return;
}
if (addr + length > MEMORY_SIZE) {
send_packet(client_sock, "E01"); // Error: Out of bounds
return;
}
// Convert hex data to binary and write to memory
for (unsigned long i = 0; i < length; i++) {
unsigned int byte;
if (sscanf(&data[i * 2], "%2x", &byte) != 1) {
send_packet(client_sock, "E00"); // Invalid hex
return;
}
memory_array[addr + i] = (uint8_t)byte;
}
// Send OK if successful
send_packet(client_sock, "OK");
} else if (strcmp(packet, "?") == 0) {
// Signal reporting (dummy response)
send_packet(client_sock, "S05"); // Example: Signal 5 (SIGTRAP)
} else if (strcmp(packet, "k") == 0) {
// Kill command, send OK and terminate
send_packet(client_sock, "OK");
} else if (packet[0] == 'q') {
// Handle queries
handle_query(client_sock, packet);
} else if (packet[0] == 'v') {
// Handle extended commands
handle_v_command(client_sock, packet);
} else {
// Unknown command, send empty response
send_packet(client_sock, "");
}
}
// Handle communication with the GDB client
void handle_client(int client_sock) {
char packet_buffer[4096];
uint8_t received_checksum;
while (1) {
// Receive packet from GDB
char *packet = recv_packet(client_sock, packet_buffer, sizeof(packet_buffer), &received_checksum);
if (!packet) {
printf("Connection closed or packet error.\n");
break;
}
printf("Received packet from GDB client: %s\n", packet);
// Calculate checksum
uint8_t calculated_checksum = calculate_checksum(packet);
if (calculated_checksum != received_checksum) {
printf("Checksum mismatch: calculated %02x, received %02x\n", calculated_checksum, received_checksum);
send_ack(client_sock, '-'); // Negative acknowledgment
continue;
} else {
send_ack(client_sock, '+'); // Positive acknowledgment
}
// Handle GDB commands
handle_gdb_command(client_sock, packet);
}
}
// Send a packet to GDB
void send_packet(int client_sock, const char *data) {
char packet[4096];
uint8_t cksum = calculate_checksum(data);
// Format the packet: $<data>#<checksum>
snprintf(packet, sizeof(packet), "$%s#%02x", data, cksum);
// Send the packet
ssize_t sent = send(client_sock, packet, strlen(packet), 0);
if (sent < 0) {
perror("Failed to send packet");
} else {
// Debug: Print sent packet
printf("Sent packet: %s\n", packet);
}
}
// Receive a packet from GDB
char *recv_packet(int client_sock, char *buffer, size_t buffer_size, uint8_t *received_checksum) {
ssize_t n;
char c;
size_t idx = 0;
int reading = 0;
while ((n = recv(client_sock, &c, 1, 0)) > 0) {
if (c == '$') {
// Start of a packet
reading = 1;
idx = 0;
} else if (reading && c == '#') {
// End of packet, read checksum
char checksum_str[3] = {0};
if (recv(client_sock, &checksum_str[0], 2, 0) != 2) {
return NULL; // Failed to read checksum
}
*received_checksum = (uint8_t)strtol(checksum_str, NULL, 16);
buffer[idx] = '\0'; // Null-terminate the packet
return buffer;
} else if (reading) {
if (idx < buffer_size - 1) {
buffer[idx++] = c;
} else {
// Buffer overflow, invalid packet
return NULL;
}
}
}
return NULL; // Connection closed or error
}
// Calculate checksum for GDB packet
uint8_t calculate_checksum(const char *data) {
uint8_t sum = 0;
while (*data) {
sum += (uint8_t)*data++;
}
return sum;
}
// Send acknowledgment ('+' or '-')
void send_ack(int client_sock, char ack) {
char ack_packet[2] = {ack, '\0'};
ssize_t sent = send(client_sock, &ack_packet[0], 1, 0);
if (sent < 0) {
perror("Failed to send acknowledgment");
} else {
printf("Sent acknowledgment: %c\n", ack);
}
}
// Handle 'q' queries
void handle_query(int client_sock, const char *packet) {
if (strcmp(packet, "qSupported") == 0) {
// Respond with supported features
// Example: "PacketSize=4000"
send_packet(client_sock, "PacketSize=4000");
} else if (strncmp(packet, "qSupported+", 11) == 0) {
// Handle specific supported queries if needed
// For simplicity, respond with empty
send_packet(client_sock, "");
} else {
// Unknown query, respond with empty
send_packet(client_sock, "");
}
}
// Handle 'v' extended commands
void handle_v_command(int client_sock, const char *packet) {
if (strcmp(packet, "vMustReplyEmpty") == 0) {
// According to GDB RSP, reply with empty packet
send_packet(client_sock, "");
} else {
// Unknown 'v' command, respond with empty
send_packet(client_sock, "");
}
}