User's Guide

The module gcc.cdecl provides a function gcc.cdecl.declare, which formats a GCC declaration or type node as a string of C code. In the following examples, we will learn how to use this function to extract functions, variables and types from the operating system’s implementation of the POSIX C API.

1 Functions and variables

To extract function and variable declarations from a C header file, we declare specially-prefixed variables in a C source file that point to the declarations. The source file is parsed using GCC and a Lua plugin script that selects these variables by matching their common name prefix.

Create a file func.c with the following C source code:

#define _XOPEN_SOURCE 700
#include <unistd.h>

#define cdecl_func(name) __typeof__(name) *cdecl_func__ ## name = &name;
#define cdecl_var cdecl_func

/* https://pubs.opengroup.org/onlinepubs/9699919799/functions/optarg.html */
cdecl_func(getopt)
cdecl_var(optarg)
cdecl_var(opterr)
cdecl_var(optind)
cdecl_var(optopt)

Create a file func.lua with the following Lua source code:

local gcc = require("gcc")
local cdecl = require("gcc.cdecl")

-- send assembler output to /dev/null
gcc.set_asm_file_name(gcc.HOST_BIT_BUCKET)

-- invoke Lua function after translation unit has been parsed
gcc.register_callback(gcc.PLUGIN_FINISH_UNIT, function()
  -- get global variables in reverse order of declaration
  local nodes = gcc.get_variables()
  for i = #nodes, 1, -1 do
    local name = nodes[i]:name():value()
    if name:match("^cdecl_func__") then
      -- get function or variable pointed to by initial value
      local decl = nodes[i]:initial():operand()
      -- output function or variable declaration
      print(cdecl.declare(decl) .. ";")
    end
  end
end)

Parse the C source file using the GCC Lua plugin:

gcc -S -std=c99 -Wall func.c -fplugin=../gcc-lua/gcc/gcclua.so -fplugin-arg-gcclua-script=func.lua

The output contains the declarations captured in the C source file:

int getopt(int, char *const *, const char *) __attribute__((__nothrow__));
extern char *optarg;
extern int opterr;
extern int optind;
extern int optopt;

2 Assembler names

Many C libraries use the C preprocessor to substitute documented API names with internal ABI names. For example, the POSIX function basename is implemented as __xpg_basename in the GNU C library.

gcc.cdecl.declare may be passed a function as a second argument. This function is invoked on any declaration or type encountered upon formatting the first argument, and may return a string that overrides the parsed name. In this example, we override the internal ABI name of a declaration with its documented API name.

Create a file asm.c with the following C source code:

#define _XOPEN_SOURCE 700
#include <libgen.h>

#define cdecl_func(name) __typeof__(name) *cdecl_func__ ## name = &name;
#define cdecl_var cdecl_func

/* https://pubs.opengroup.org/onlinepubs/9699919799/functions/basename.html */
cdecl_func(basename)
/* https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirname.html */
cdecl_func(dirname)

Create a file asm.lua with the following Lua source code:

local gcc = require("gcc")
local cdecl = require("gcc.cdecl")

-- send assembler output to /dev/null
gcc.set_asm_file_name(gcc.HOST_BIT_BUCKET)

-- invoke Lua function after translation unit has been parsed
gcc.register_callback(gcc.PLUGIN_FINISH_UNIT, function()
  -- get global variables in reverse order of declaration
  local nodes = gcc.get_variables()
  for i = #nodes, 1, -1 do
    local name = nodes[i]:name():value()
    local name = name:match("^cdecl_func__(.+)")
    if name then
      -- get function or variable pointed to by initial value
      local decl = nodes[i]:initial():operand()
      -- output function or variable declaration with API name
      print(cdecl.declare(decl, function(node)
        if node == decl then return name end
      end) .. ";")
    end
  end
end)

Parse the C source file using the GCC Lua plugin:

gcc -S -std=c99 -Wall asm.c -fplugin=../gcc-lua/gcc/gcclua.so -fplugin-arg-gcclua-script=asm.lua

The output contains the declarations captured in the C source file:

char *basename(char *) __asm__("__xpg_basename") __attribute__((__nothrow__));
char *dirname(char *) __attribute__((__nothrow__));

Declarations with a differing assembler name include a GCC asm label.

3 Type declarations

To extract type declarations, we declare variables with a corresponding pointee type. We use pointer variables to avoid warnings about missing initialisation for constant types.

Create a file type.c with the following C source code:

#define _XOPEN_SOURCE 700
#include <time.h>

#define cdecl_type(name) name *cdecl_type__ ## name;

cdecl_type(clockid_t)
cdecl_type(time_t)

Create a file type.lua with the following Lua source code:

local gcc = require("gcc")
local cdecl = require("gcc.cdecl")

-- send assembler output to /dev/null
gcc.set_asm_file_name(gcc.HOST_BIT_BUCKET)

-- invoke Lua function after translation unit has been parsed
gcc.register_callback(gcc.PLUGIN_FINISH_UNIT, function()
  -- get global variables in reverse order of declaration
  local nodes = gcc.get_variables()
  for i = #nodes, 1, -1 do
    local name = nodes[i]:name():value()
    local name = name:match("^cdecl_type__(.+)")
    if name then
      -- get type declaration of pointee type
      local decl = nodes[i]:type():type():name()
      -- output type declaration with API name
      print(cdecl.declare(decl, function(node)
        if node == decl then return name end
      end) .. ";")
    end
  end
end)

Parse the C source file using the GCC Lua plugin:

gcc -S -std=c99 -Wall type.c -fplugin=../gcc-lua/gcc/gcclua.so -fplugin-arg-gcclua-script=type.lua

The output contains the declarations captured in the C source file:

typedef int clockid_t;
typedef long int time_t;

4 Struct, union and enum types

For a type declaration, gcc.cdecl.declare only expands anonymous struct, union and enum types, for the purpose of allowing forward declarations, and to avoid duplicate definitions. Instead of the type declaration, we format the type itself to define a named struct, union or enum type.

Create a file struct.c with the following C source code:

#define _XOPEN_SOURCE 700
#include <time.h>

#define cdecl_struct(name) struct name *cdecl_struct__ ## name;

cdecl_struct(timespec)

Create a file struct.lua with the following Lua source code:

local gcc = require("gcc")
local cdecl = require("gcc.cdecl")

-- send assembler output to /dev/null
gcc.set_asm_file_name(gcc.HOST_BIT_BUCKET)

-- invoke Lua function after translation unit has been parsed
gcc.register_callback(gcc.PLUGIN_FINISH_UNIT, function()
  -- get global variables in reverse order of declaration
  local nodes = gcc.get_variables()
  for i = #nodes, 1, -1 do
    local name = nodes[i]:name():value()
    local name = name:match("^cdecl_struct__(.+)")
    if name then
      -- get pointee type
      local type = nodes[i]:type():type()
      -- output type with API name
      print(cdecl.declare(type, function(node)
        if node == type then return name end
      end) .. ";")
    end
  end
end)

Parse the C source file using the GCC Lua plugin:

gcc -S -std=c99 -Wall struct.c -fplugin=../gcc-lua/gcc/gcclua.so -fplugin-arg-gcclua-script=struct.lua

The output contains the definitions of types captured in the C source file:

struct timespec {
  long int tv_sec;
  long int tv_nsec;
};

5 Integer constants

In this example, we extract and declare integer constants, which is the only kind of C constant supported by LuaJIT FFI as of LuaJIT version 2.0.

Create a file const.c with the following C source code:

#define _XOPEN_SOURCE 700
#include <sys/resource.h>

#define cdecl_const(name) __typeof__(name) cdecl_const__ ## name = name;

/* https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html */
cdecl_const(RLIM_INFINITY)
cdecl_const(RLIMIT_CORE)
cdecl_const(RLIMIT_CPU)
cdecl_const(RLIMIT_DATA)
cdecl_const(RLIMIT_FSIZE)
cdecl_const(RLIMIT_NOFILE)
cdecl_const(RLIMIT_STACK)

Create a file const.lua with the following Lua source code:

local gcc = require("gcc")

-- send assembler output to /dev/null
gcc.set_asm_file_name(gcc.HOST_BIT_BUCKET)

-- invoke Lua function after translation unit has been parsed
gcc.register_callback(gcc.PLUGIN_FINISH_UNIT, function()
  -- get global variables in reverse order of declaration
  local nodes = gcc.get_variables()
  for i = #nodes, 1, -1 do
    local name = nodes[i]:name():value()
    local name = name:match("^cdecl_const__(.+)")
    if name then
      -- extract constant value
      local value = nodes[i]:initial():value()
      -- output constant with API name
      print(("static const int %s = %d;"):format(name, value))
    end
  end
end)

Parse the C source file using the GCC Lua plugin:

gcc -S -std=c99 -Wall const.c -fplugin=../gcc-lua/gcc/gcclua.so -fplugin-arg-gcclua-script=const.lua

The output contains the definitions of constants captured in the C source file:

static const int RLIM_INFINITY = -1;
static const int RLIMIT_CORE = 4;
static const int RLIMIT_CPU = 0;
static const int RLIMIT_DATA = 2;
static const int RLIMIT_FSIZE = 1;
static const int RLIMIT_NOFILE = 7;
static const int RLIMIT_STACK = 3;