GDB Guides Series:

  1. GDB Guide Part 1 - Basics
  2. GDB Guide Part 2 - Breakpoints & Linux Calling Conventions
  3. GDB Guide Part 3 - Process Maps
  4. GDB Guide Part 4 - Examining Memory
  5. GDB Guide Part 5 - Stepping
  6. GDB Guide Part 6 - Automation
  7. GDB Guide Part 7 - Custom Commands
  8. GDB Guide Part 8 - Invoking Function Calls

1. Introduction

It’s been quite a while hasn’t it? I hope you have been doing well and taking care of yourself. I recently had some junior colleagues asking me how it is possible to directly invoke certain functions while using GDB. Thus, I thought it would be a great opportunity to share some useful tips.

We will be looking at how to invoke different functions of different return types using a simple example: gdb_invoke_function.c

Let’s get started!

2. Syntax

When invoking functions manually in GDB, you have full control over function calls just like in C. You have to explicitly cast the function return type when invoking any function call. Optionally, you can also cast a type to the argument.

To call a function, use:

call (return_type)function((arg_type)arg1, (arg_type)arg2, ...)

# Example: 
# └── call (int)add((int)$arg1, (int)$arg2)
Syntax for Invoking Function Call

If the function returns a value and you want to store it in a GDB variable for later use:

set $ret_val = (return_type)function((arg_type)arg1, (arg_type)arg2, ...)

# Example: 
# └── set $ret = (int)add((int)$arg1, (int)$arg2)
Assigning Return Value to GDB Variable

For cases where the function symbol is stripped, we can also directly invoke the function call through the known function address.

call (return_type)function_addr((arg_type)arg1, (arg_type)arg2, ...)

# Example: 
# └── call (int)0x4004bc((int)$arg1, (int)$arg2)
Invoking Function Call Through Known Address

3. Examples

This section of the guide will solely reference gdb_invoke_function.c.

3.1 No Arguments

For a start, let’s invoke the do_nothing() function which takes in no arguments and does not return anything.

(gdb) call (void)do_nothing()
Calling do_nothing() Function

3.2 One Argument

Next, we can invoke the hello(const char *name) function which takes in a string as its only argument.

(gdb) call (void)hello("Gerald")
Calling hello(const char *name) Function

3.3 Two Arguments

Similarly, we can do the same for invoking the add(int a, int b) function which takes in 2 integers and returns the sum.

(gdb) set $sum = (int)add(24, 10)
(gdb) p $sum
$1 = 34
Calling add(int a, int b) Function

3.4 Struct Arguments

GDB is not just limited to primitive types. You can also pass struct pointers into functions, even in stripped binaries or cases where you no longer have symbol/type information.

In our example, we want to invoke get_person_name(person_t *person), which returns a pointer to the name field of a person_t struct.

Since GDB doesn’t know about person_t as a type (especially in stripped binaries), we can manually construct the struct in memory by calculating offsets from static analysis or DWARF debug info.

Taking a look at the definition of the person_t struct, we can manually calculate the size of the struct, which ends up being 259 bytes.

typedef struct {
    uint16_t id;          // 2 Bytes
    char name[256];       // 256 Bytes
    uint8_t is_deceased;  // 1 Byte
} person_t;               /* Total: 259 Bytes */
Definition of person_t

Thus, we allocate 259 bytes in the heap and assign the return address to a variable to store the pointer.

(gdb) set $person = (uint64_t)malloc(259)
Allocating 259 Bytes in Heap

Next, we populate the struct fields one by one. Set the id field (offset 0x0):

(gdb) set *(uint16_t *)$person = 1
Setting id Field at Offset 0x0

Set the name field (offset 0x2):

(gdb) call (void)strcpy((char *)($person + 0x2), "Gerald")
# OR
(gdb) call (int)snprintf((char *)($person + 0x2), 256, "%s", "Gerald")
Setting name Field at Offset 0x2

Set the is_deceased field (offset 0x102):

(gdb) set *(uint8_t *)($person + 0x102) = 0
Setting is_deceased Field at Offset 0x102

Once the struct is initialized, we can pass it to the function and print the returned name.

(gdb) call (char *)get_person_name((uint64_t *)$person)
$4 = 0x110b3ac2 "Gerald"
Calling get_person_name(person_t *person) Function

As a good practice, we will free up our struct in the heap after we are done.

(gdb) call free($person)
Freeing Struct in Heap Memory

4. Conclusion

Manually invoking functions within GDB opens up an entirely new level of power during debugging and reverse engineering. Whether you’re verifying program behavior, modifying runtime values, or analyzing stripped binaries with no symbols, function calls allow for deep insight and control.

We saw how you can:

  • Call functions just like you would in C.
  • Assign return values to GDB variables.
  • Pass struct pointers by manually crafting memory layout, even without debug symbols.

These techniques are especially useful in situations where:

  • You’re dealing with stripped binaries.
  • You need to simulate program flow or reconstruct missing behavior.
  • You want to dynamically patch or inspect runtime behavior with minimal friction.

Understanding how to construct valid arguments and call functions correctly allows you to turn GDB from a passive observer into an active debugger. This knowledge can also serve as the foundation for scripting more powerful automation through Python GDB extensions or agents.

That’s all for today! Happy reversing. 🤓

5. Resources

  1. gdb_invoke_function.c

GDB Guides Series:

  1. GDB Guide Part 1 - Basics
  2. GDB Guide Part 2 - Breakpoints & Linux Calling Conventions
  3. GDB Guide Part 3 - Process Maps
  4. GDB Guide Part 4 - Examining Memory
  5. GDB Guide Part 5 - Stepping
  6. GDB Guide Part 6 - Automation
  7. GDB Guide Part 7 - Custom Commands
  8. GDB Guide Part 8 - Invoking Function Calls