how to stub fgets in C while using Google Unit Test - c

I have currently been assigned to do unit tests on some problems that I've done during an introductory bootcamp, and I'm having problems understanding the concept of 'stub' or 'mock'.
I'm using Google Unit Test, and the problems from the bootcamp are solved in C.
int validate_input(uint32_t * input_value) {
char input_buffer[1024] = {0};
char * endptr = NULL;
int was_read_correctly = 1;
printf("Give the value for which to print the bits: ");
/*
* Presuming wrong input from user, it does not signal:
* - number that exceeds the range of uint_32 (remains to be fixed)
* For example: 4294967295 is the max value of uint_32 ( and this can be also confirmed by the output )
* If bigger numbers are entered the actual value seems to reset ( go back to 0 and upwards.)
*/
if (NULL == fgets(input_buffer, 1024, stdin)) {
was_read_correctly = 0;
} else {
if ('-' == input_buffer[0]) {
fprintf(stderr, "Negative number not allowed.\n");
was_read_correctly = 0;
}
}
errno = 0;
if (1 == was_read_correctly) {
* input_value = strtol(input_buffer, & endptr, 10);
if (ERANGE == errno) {
fprintf(stderr, "Sorry, this number is too small or too large.\n");
was_read_correctly = 0;
} else if (endptr == input_buffer) {
fprintf(stderr, "Incorrect input.\n(Entered characters or characters and digits.)\n");
was_read_correctly = 0;
} else if ( * endptr && '\n' != * endptr) {
fprintf(stderr, "Input didn't get wholely converted.\n(Entered digits and characters)\n");
was_read_correctly = 0;
}
} else {
fprintf(stderr, "Input was not read correctly.\n");
was_read_correctly = 0;
}
return was_read_correctly;
}
How should I think/plan the process of stubbing a function like fgets/malloc in C? And, if it isn't too much, how a function like this should be thought to test?

Disclaimer: This is just one way to mock C functions for GoogleTest. There are other methods for sure.
The problem to mock C functions lays in the way GoogleTest works. All its cool functionality is based on deriving a C++ class to mock and overriding its methods. These methods must be virtual, too. But C function are no members of any class, left alone of being virtual.
The way we found and use with success it to provide a kind of wrapper class that includes methods that have the same prototype as the C functions. Additionally this class holds a pointer to an instance of itself as a static class variable. In some sense this resembles the Singleton pattern, with all its characteristics, for good or bad.
Each test instantiates an object of this class and uses this object for the common checks.
Finally the C functions are implemented as stubs that call the single instance's method of the same kind.
Let's say we have these C functions:
// cfunction.h
#ifndef C_FUNCTION_H
#define C_FUNCTION_H
extern "C" void cf1(int p1, void* p2);
extern "C" int cf2(void);
#endif
Then the header file for the mocking class is:
// CFunctionMock.h
#ifndef C_FUNCTION_MOCK_H
#define C_FUNCTION_MOCK_H
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "cfunction.h"
class CFunctionMock
{
public:
static CFunctionMock* instance;
CFunctionMock() {
instance = this;
}
~CFunctionMock() {
instance = nullptr;
}
MOCK_METHOD(void, cf1, (int p1, void* p2));
MOCK_METHOD(int, cf2, (void));
};
#endif
And this is the implementation of the mocking class, including the replacing C functions. All the functions check that the single instance exists.
// CFunctionMock.cpp
#include "CFunctionMock.h"
CFunctionMock* CFunctionMock::instance = nullptr;
extern "C" void cf1(int p1, void* p2) {
ASSERT_NE(CFunctionMock::instance, nullptr);
CFunctionMock::instance->cf1(p1, p2);
}
extern "C" int cf2(void) {
if (CFunctionMock::instance == nullptr) {
ADD_FAILURE() << "CFunctionMock::instance == nullptr";
return 0;
}
return CFunctionMock::instance->cf2();
}
On non-void function you can't use ASSERT_NE because it quits on an error with a simple return. Therefore the check for an existing instance is a bit more elaborated. You should think of a good default value to return, too.
Now we get to write some test.
// SomeTest.cpp
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::_;
using ::testing::Return;
#include "CFunctionMock.h"
#include "module_to_test.h"
TEST(AGoodTestSuiteName, AndAGoodTestName) {
CFunctionMock mock;
EXPECT_CALL(mock, cf1(_, _))
.Times(0);
EXPECT_CALL(mock, cf2())
.WillRepeatedly(Return(23));
// any call of module_to_test that calls (or not) the C functions
// any EXPECT_...
}
EDIT
I was reading the question once more and came to the conclusion that a more direct example is necessary. So here we go! I like to use as much of the magic behind Googletest because it makes extensions so much easier. Working around it feels like working against it.
Oh, my system is Windows 10 with MinGW64.
I'm a fan of Makefiles:
TESTS := Test
WARNINGLEVEL := -Wall -Wextra
CC := gcc
CFLAGS := $(WARNINGLEVEL) -g -O3
CXX := g++
CXXFLAGS := $(WARNINGLEVEL) -std=c++11 -g -O3 -pthread
LD := g++
LDFLAGS := $(WARNINGLEVEL) -g -pthread
LIBRARIES := -lgmock_main -lgtest -lgmock
GTESTFLAGS := --gtest_color=no --gtest_print_time=0
all: $(TESTS:%=%.exe)
run: all $(TESTS:%=%.log)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $#
%.o: %.cpp
$(CXX) $(CXXFLAGS) -I./include -c $< -o $#
%.exe: %.o
$(LD) $(LDFLAGS) $^ -L./lib $(LIBRARIES) -o $#
%.log: %.exe
$< $(GTESTFLAGS) > $# || type $#
Test.exe: module_to_test.o FgetsMock.o
These Makefiles make it easy to add more tests, modules, anything, and document all options. Extend it to your liking.
Module to Test
To get no warning, I had to extend the provided source:
// module_to_test.c
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "module_to_test.h"
// all the rest is as in the OP's source...
And of course we need a header file:
// module_to_test.h
#include <stdint.h>
int validate_input(uint32_t *input_value);
The Mock Class
The mock class is modelled after the example above. Do enable "feeding" the string I added an parameterized action.
// FgetsMock.h
#ifndef FGETS_MOCK_H
#define FGETS_MOCK_H
#include <cstring>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
ACTION_P(CopyFromSource, source)
{
memcpy(arg0, source, arg1);
}
class FgetsMock
{
public:
static FgetsMock* instance;
FgetsMock()
{
instance = this;
}
~FgetsMock()
{
instance = nullptr;
}
MOCK_METHOD(char*, fgets, (char*, int, FILE*));
};
#endif
Its implementation file is straight forward and provides the mocked C function.
// FgetsMock.cpp
#include <stdio.h>
#include "FgetsMock.h"
FgetsMock* FgetsMock::instance = nullptr;
extern "C" char* fgets(char* str, int num, FILE* stream)
{
if (FgetsMock::instance == nullptr)
{
ADD_FAILURE() << "FgetsMock::instance == nullptr";
return 0;
}
return FgetsMock::instance->fgets(str, num, stream);
}
Implementing Some Tests
Here are some examples for tests. Unfortunately the module-to-test uses stdout and stderr that are not so simple to catch and test. You might like to read about "death tests" or provide your own method of redirection. In the core, the design of the function is not that good, because it did not take testing into account.
// Test.cpp
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::_;
using ::testing::DoAll;
using ::testing::Ge;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::ReturnArg;
#include "FgetsMock.h"
extern "C"
{
#include "module_to_test.h"
}
TEST(ValidateInput, CorrectInput)
{
const char input[] = "42";
const int input_length = sizeof input;
FgetsMock mock;
uint32_t number;
EXPECT_CALL(mock, fgets(NotNull(), Ge(input_length), stdin))
.WillOnce(DoAll(
CopyFromSource(input),
ReturnArg<0>()
));
int result = validate_input(&number);
EXPECT_EQ(result, 1);
EXPECT_EQ(number, 42U);
}
TEST(ValidateInput, InputOutputError)
{
FgetsMock mock;
uint32_t dummy;
EXPECT_CALL(mock, fgets(_, _, _))
.WillOnce(Return(nullptr));
int result = validate_input(&dummy);
EXPECT_EQ(result, 0);
}
TEST(ValidateInput, NegativeInput)
{
const char input[] = "-23";
const int input_length = sizeof input;
FgetsMock mock;
uint32_t dummy;
EXPECT_CALL(mock, fgets(NotNull(), Ge(input_length), stdin))
.WillOnce(DoAll(
CopyFromSource(input),
ReturnArg<0>()
));
int result = validate_input(&dummy);
EXPECT_EQ(result, 0);
}
TEST(ValidateInput, RangeError)
{
const char input[] = "12345678901";
const int input_length = sizeof input;
FgetsMock mock;
uint32_t dummy;
EXPECT_CALL(mock, fgets(NotNull(), Ge(input_length), stdin))
.WillOnce(DoAll(
CopyFromSource(input),
ReturnArg<0>()
));
int result = validate_input(&dummy);
EXPECT_EQ(result, 0);
}
TEST(ValidateInput, CharacterError)
{
const char input[] = "23fortytwo";
const int input_length = sizeof input;
FgetsMock mock;
uint32_t dummy;
EXPECT_CALL(mock, fgets(NotNull(), Ge(input_length), stdin))
.WillOnce(DoAll(
CopyFromSource(input),
ReturnArg<0>()
));
int result = validate_input(&dummy);
EXPECT_EQ(result, 0);
}
Building and Running the Tests
This is the output of my (Windows) console when building freshly and testing:
> make run
gcc -Wall -Wextra -g -O3 -c module_to_test.c -o module_to_test.o
g++ -Wall -Wextra -std=c++11 -g -O3 -pthread -I./include -c FgetsMock.cpp -o FgetsMock.o
g++ -Wall -Wextra -std=c++11 -g -O3 -pthread -I./include -c Test.cpp -o Test.o
g++ -Wall -Wextra -g -pthread Test.o module_to_test.o FgetsMock.o -L./lib -lgmock_main -lgtest -lgmock -o Test.exe
Test.exe --gtest_color=no --gtest_print_time=0 > Test.log || type Test.log
Input was not read correctly.
Negative number not allowed.
Input was not read correctly.
Sorry, this number is too small or too large.
Input didn't get wholely converted.
(Entered digits and characters)
rm Test.o
You see the output of stderr of the C function.
And this is the recorded log, see the Makefile how it is produced.
Running main() from gmock_main.cc
[==========] Running 5 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 5 tests from ValidateInput
[ RUN ] ValidateInput.CorrectInput
Give the value for which to print the bits: [ OK ] ValidateInput.CorrectInput
[ RUN ] ValidateInput.InputOutputError
Give the value for which to print the bits: [ OK ] ValidateInput.InputOutputError
[ RUN ] ValidateInput.NegativeInput
Give the value for which to print the bits: [ OK ] ValidateInput.NegativeInput
[ RUN ] ValidateInput.RangeError
Give the value for which to print the bits: [ OK ] ValidateInput.RangeError
[ RUN ] ValidateInput.CharacterError
Give the value for which to print the bits: [ OK ] ValidateInput.CharacterError
[----------] Global test environment tear-down
[==========] 5 tests from 1 test suite ran.
[ PASSED ] 5 tests.
Because of the output on stdout it is mixed up with Googletest's output.

I have managed to solve this issue in the following way:
header file for the stub function:
#ifndef STUBS_H_
#define STUBS_H_
#include "../src/p1.h"
char* fgets_stub(char *s, int size, FILE *stream);
#define fgets fgets_stub
#include "../src/p1.c"
char* fgets_RET;
#endif
implementation of stub function:
#include "stubs.h"
char* fgets_stub(char *s, int size, FILE *stream)
{
if (NULL != fgets_RET)
{
strcpy(s,fgets_RET);
}
return fgets_RET;
}
how to test in test.cpp:
TEST(ValidateInput,CorrectionTest)
{
uint32_t tester = 0;
char* dummy_char = new char[NUM_OF_BITS];
strcpy(dummy_char,"39131");
cout<<dummy_char;
fgets_RET = dummy_char;
ASSERT_EQ(1,validate_input(&tester));
}
if the person that tests wishes to force NULL return of fgets:
TEST(ValidateInput,CorrectionTest)
{
uint32_t tester = 0;
fgets_RET = NULL;
ASSERT_EQ(0,validate_input(&tester));
}

Related

Attribute for ignoring "too many arguments in call to 'func'"

I am creating a light test framework. For my local debugging in linux with gcc and clang, I do not get any complaints for mocking a function that has arguments, but mocking it with no arguments. eg.
add.c
int add(int a, int b) {
return a + b;
}
foo.c
#include "add.h"
int add_2(int a) {
return add(a, 2);
}
Now, in order to mock add. I simply created these macros.
testframework.h
#define DECLARE_MOCK(type, name) \
type __var_##name[255]; \
size_t __var_##name##_inc = 0; \
size_t __var_##name##_actual = 0; \
type name() { return (type)__var_##name[__var_##name##_inc++]; }
#define MOCK(name, value) __var_##name[__var_##name##_actual++] = value;
This works well on my linux machine. add(x,y) requires two arguments, but gcc or clang doesn't complain that the mock will essentially have no arguments passed to it, and works perfectly as a stand in. it's this line here type name() ...
Here is the usage. Notice I am mocking add.c capability in this test file.
#include "foo.h"
#include "testframework.h"
DECLARE_MOCK(int, add);
int main() {
DESCRIBE("things");
MOCK(add, 2);
SHOULDB("add", {
ASSERT(add(0, 2) == 2);
});
}
The issue comes in on gcc mac, which complains.
[INFO] CMD: gcc -Wall -Werror -std=c11 -O3 -o target/things tests/things.c obj/things/lib.o
tests/things.c:29:57: error: too many arguments in call to 'add' [-Werror]
SHOULDB("add", { ASSERT(add(0, 2) == 2); });
I would like to keep -Wall and -Werror, I was hoping there was an attribute I could add to the macro, which is the opposite of sentinel.

Can you directly call functions in command line?

For example, if I have the following function
void printText(char text [100]){
printf("%d", text);
}
could I then do this in the command line
printText(Hello World)
and then get my expected output as
Hello World
It depends on your shell. Some shells do support functions. In bash, the POSIX shell, and probably others, the following is the correct syntax:
printText() {
printf '%s\n' "$1"
}
printText 'Hello World'
If you meant your question literally, then no, it's not possible to call a function without even mentioning the file in which it's located. The language used to write the function is irrelevant.
But it is possible to compile a C function and have it called from the shell somehow? Yes. If you created a shared library (shared object on unixy systems or DLL on Windows) from the function, you could. It would require a tool to do so, but such a tool could exit. (Windows also supports COM objects and a number of derived techs. Some of these might even make the task easier.)
(I can't tell if such tools actually do exist or what they are because software recommendations are off-topic on StackOverflow. I will say that such a tool could be built around a library such as libffi.)
One solution would be to rely on dlopen()/LoadLibrary() and
dlsym()/GetProcAddress() but you cannot ensure the function
prototype conforms to your expectation.
A more robust solution consists in providing a lookup table filled
with functions that you know are compliant with the intended usage.
/**
gcc -std=c99 -o prog_c prog_c.c \
-pedantic -Wall -Wextra -Wconversion \
-Wc++-compat -Wwrite-strings -Wold-style-definition -Wvla \
-g -O0 -UNDEBUG -fsanitize=address,undefined
$ ./prog_c printText "Hello world"
printText --> <Hello world>
$ ./prog_c textLen "Hello world"
textLen --> 11
$ ./prog_c what "Hello world"
cannot find function 'what'
**/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
void
printText(const char *text)
{
printf("printText --> <%s>\n", text);
}
void
textLen(const char *text)
{
printf("textLen --> %d\n", (int)strlen(text));
}
typedef struct
{
const char *name;
void (*fnct)(const char *);
} TextFunction;
bool // success
call_text_function(const char *name,
const char *arg)
{
static TextFunction table[]={ {"printText", printText},
{"textLen", textLen},
{NULL, NULL} };
for(int i=0; table[i].name!=NULL; ++i)
{
if(strcmp(table[i].name, name)==0)
{
table[i].fnct(arg);
return true;
}
}
return false;
}
int
main(int argc,
char **argv)
{
if(argc!=3)
{
fprintf(stderr, "usage: %s function arg\n", argv[0]);
return 1;
}
if(!call_text_function(argv[1], argv[2]))
{
fprintf(stderr, "cannot find function '%s'\n", argv[1]);
return 1;
}
return 0;
}

Weird pointer conversion in C

I'm having trouble while writing my garbage collector in C. I give you a minimal and verifiable example for it.
The first file is in charge of dealing with the virtual machine
#include <stdlib.h>
#include <stdint.h>
typedef int32_t value_t;
typedef enum {
Lb, Lb1, Lb2, Lb3, Lb4, Lb5,
Ib, Ob
} reg_bank_t;
static value_t* memory_start;
static value_t* R[8];
value_t* engine_get_Lb(void) { return R[Lb]; }
value_t engine_run() {
memory_start = memory_get_start();
for (reg_bank_t pseudo_bank = Lb; pseudo_bank <= Lb5; ++pseudo_bank)
R[pseudo_bank] = memory_start + (pseudo_bank - Lb) * 32;
value_t* block = memory_allocate();
}
Then I have the actual garbage collector, the minimized code is:
#include <stdlib.h>
#include <stdint.h>
typedef int32_t value_t;
static value_t* memory_start = NULL;
void memory_setup(size_t total_byte_size) {
memory_start = calloc(total_byte_size, 1);
}
void* memory_get_start() { return memory_start; }
void mark(value_t* base){
value_t vbase = 0;
}
value_t* memory_allocate() {
mark(engine_get_Lb());
return engine_get_Lb();
}
Finally, minimal main is:
int main(int argc, char* argv[]) {
memory_setup(1000000);
engine_run();
return 0;
}
The problem I'm getting with gdb is that if I print engine_get_Lb() I get the address (value_t *) 0x7ffff490a800 while when printing base inside of the function mark I get the address (value_t *) 0xfffffffff490a800.
Any idea why this is happening?
Complementary files that may help
The makefile
SHELL=/bin/bash
SRCS=src/engine.c \
src/main.c \
src/memory_mark_n_sweep.c
CFLAGS_COMMON=-std=c11 -fwrapv
CLANG_SAN_FLAGS=-fsanitize=address
# Clang warning flags
CLANG_WARNING_FLAGS=-Weverything \
-Wno-format-nonliteral \
-Wno-c++98-compat \
-Wno-gnu-label-as-value
# Flags for debugging:
CFLAGS_DEBUG=${CFLAGS_COMMON} -g ${CLANG_SAN_FLAGS} ${CLANG_WARNING_FLAGS}
# Flags for maximum performance:
CFLAGS_RELEASE=${CFLAGS_COMMON} -O3 -DNDEBUG
CFLAGS=${CFLAGS_DEBUG}
all: vm
vm: ${SRCS}
mkdir -p bin
clang ${CFLAGS} ${LDFLAGS} ${SRCS} -o bin/vm
File with instructions .asm
5c190000 RALO(Lb,25)
value_t* memory_allocate() {
mark(engine_get_Lb());
return engine_get_Lb();
}
engine_get_Lb is not declared before use. It is assumed by the compiler to return int, per an antiquated and dangerous rule of the C language. It was deprecated in the C standard for quite some time, and now is finally removed.
Create a header file with declarations of all your global functions, and #include it in all your source files.
Your compiler should have at least warned you about this error at its default settings. If it did, you should have read and completely understood the warnings before continuing. If it didn't, consider an upgrade. If you cannot upgrade, permanently add -Wall -Wextra -Werror to your compilation flags. Consider also -Wpedantic and -std=c11.

header files and multiple c programs

I am trying to use two .c files together. I am lost at how to do this, I have a simple setup for each file but I get a undefined reference to format_lines error when I try to compile. Any help would be muchly appreciated;
formatter.h
#ifndef _FORMATTER_H_
#define _FORMATTER_H_
#include <stdio.h>
char **format_file(FILE *);
char **format_lines(char **, int);
void test();
#endif
formatter.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "formatter.h"
char **format_file(FILE *infile) {
return NULL;
}
char **format_lines(char **lines, int num_lines) {
char **result = NULL;
#ifdef DEBUG
result = (char **)malloc(sizeof(char *) * 2);
if (result == NULL) {
return NULL;
}
result[0] = (char *)malloc(sizeof(char) * 80);
if (result[0] == NULL) {
return NULL;
}
strncpy(result[0], "(machine-like voice) EXTERMINATE THEM!", 79);
result[1] = (char *)malloc(sizeof(char) * 2);
if (result[1] == NULL) {
return NULL;
}
result[1][0] = '\0';
#endif
}
void test(){
print("here");
}
and sengfmt.c
#include <stdio.h>
#include <stdlib.h>
#include "formatter.h"
int main(int argc, char *argv[]) {
test();
#ifdef DEBUG
printf("%s does nothing right now.\n", argv[0]);
#endif
exit(0);
}
When I try to compile, I just type this.
$ gcc sengfmt3.c
/tmp/cc7Ttgne.o: In function `main':
sengfmt3.c:(.text+0x15): undefined reference to `test'
collect2: ld returned 1 exit status
I suspect that your main used to try to call format_lines
You need to do this
gcc formatter.c sendgfmt.c -o myprog
You must list all the c files that you want compiled together
If you have code in multiple source files, then you need to build with all the source files.
There are two ways of doing this:
Compile and link all source files using using one command:
$ gcc sengfmt3.c someOtherSourceFile.c someThirdSourceFile.c
First make object files of all source files, and then link the object files together. This is more work, but if you have a makefile or other build-system it will be better since only the modified source files will be recompiled, and might save you some build-time:
$ gcc -c sengfmt3.c
$ gcc -c someOtherSourceFile.c
$ gcc -c someThirdSourceFile.c
$ gcc sengfmt.o someOtherSourceFile.o someThirdSorceFile.o
Note the command-line option -c for the compilation, this tells GCC to generate object files. Also note that for the linking command (the last one) the file extensions have changed from .c to .o.
The command in point 1 does this internally, using temporary files which are removed when done.

Can a running C program access its own symbol table?

I have a linux C program that handles request sent to a TCP socket (bound to a particular port). I want to be able to query the internal state of the C program via a request to that port, but I dont want to hard code what global variables can be queried. Thus I want the query to contain the string name of a global and the C code to look that string up in the symbol table to find its address and then send its value back over the TCP socket. Of course the symbol table must not have been stripped. So can the C program even locate its own symbol table, and is there a library interface for looking up symbols given their name? This is an ELF executable C program built with gcc.
This is actually fairly easy. You use dlopen / dlsym to access symbols. In order for this to work, the symbols have to be present in the dynamic symbol table. There are multiple symbol tables!
#include <dlfcn.h>
#include <stdio.h>
__attribute__((visibility("default")))
const char A[] = "Value of A";
__attribute__((visibility("hidden")))
const char B[] = "Value of B";
const char C[] = "Value of C";
int main(int argc, char *argv[])
{
void *hdl;
const char *ptr;
int i;
hdl = dlopen(NULL, 0);
for (i = 1; i < argc; ++i) {
ptr = dlsym(hdl, argv[i]);
printf("%s = %s\n", argv[i], ptr);
}
return 0;
}
In order to add all symbols to the dynamic symbol table, use -Wl,--export-dynamic. If you want to remove most symbols from the symbol table (recommended), set -fvisibility=hidden and then explicitly add the symbols you want with __attribute__((visibility("default"))) or one of the other methods.
~ $ gcc dlopentest.c -Wall -Wextra -ldl
~ $ ./a.out A B C
A = (null)
B = (null)
C = (null)
~ $ gcc dlopentest.c -Wall -Wextra -ldl -Wl,--export-dynamic
~ $ ./a.out A B C
A = Value of A
B = (null)
C = Value of C
~ $ gcc dlopentest.c -Wall -Wextra -ldl -Wl,--export-dynamic -fvisibility=hidden
~ $ ./a.out A B C
A = Value of A
B = (null)
C = (null)
Safety
Notice that there is a lot of room for bad behavior.
$ ./a.out printf
printf = ▯▯▯▯ (garbage)
If you want this to be safe, you should create a whitelist of permissible symbols.
file: reflect.c
#include <stdio.h>
#include "reflect.h"
struct sym_table_t gbl_sym_table[1] __attribute__((weak)) = {{NULL, NULL}};
void * reflect_query_symbol(const char *name)
{
struct sym_table_t *p = &gbl_sym_table[0];
for(; p->name; p++) {
if(strcmp(p->name, name) == 0) {
return p->addr;
}
}
return NULL;
}
file: reflect.h
#include <stdio.h>
struct sym_table_t {
char *name;
void *addr;
};
void * reflect_query_symbol(const char *name);
file: main.c
just #include "reflect.h" and call reflect_query_symbol
example:
#include <stdio.h>
#include "reflect.h"
void foo(void)
{
printf("bar test\n");
}
int uninited_data;
int inited_data = 3;
int main(int argc, char *argv[])
{
int i;
void *addr;
for(i=1; i<argc; i++) {
addr = reflect_query_symbol(argv[i]);
if(addr) {
printf("%s lay at: %p\n", argv[i], addr);
} else {
printf("%s NOT found\n", argv[i], addr);
}
}
return 0;
}
file:Makefile
objs = main.o reflect.o
main: $(objs)
gcc -o $# $^
nm $# | awk 'BEGIN{ print "#include <stdio.h>"; print "#include \"reflect.h\""; print "struct sym_table_t gbl_sym_table[]={" } { if(NF==3){print "{\"" $$3 "\", (void*)0x" $$1 "},"}} END{print "{NULL,NULL} };"}' > .reflect.real.c
gcc -c .reflect.real.c -o .reflect.real.o
gcc -o $# $^ .reflect.real.o
nm $# | awk 'BEGIN{ print "#include <stdio.h>"; print "#include \"reflect.h\""; print "struct sym_table_t gbl_sym_table[]={" } { if(NF==3){print "{\"" $$3 "\", (void*)0x" $$1 "},"}} END{print "{NULL,NULL} };"}' > .reflect.real.c
gcc -c .reflect.real.c -o .reflect.real.o
gcc -o $# $^ .reflect.real.o
The general term for this sort of feature is "reflection", and it is not part of C.
If this is for debugging purposes, and you want to be able to inspect the entire state of a C program remotely, examine any variable, start and stop its execution, and so on, you might consider GDB remote debugging:
GDB offers a 'remote' mode often used when debugging embedded systems.
Remote operation is when GDB runs on one machine and the program being
debugged runs on another. GDB can communicate to the remote 'stub'
which understands GDB protocol via Serial or TCP/IP. A stub program
can be created by linking to the appropriate stub files provided with
GDB, which implement the target side of the communication
protocol. Alternatively, gdbserver can be used to remotely debug
the program without needing to change it in any way.

Resources