|
| 1 | +/* |
| 2 | + * A fun experiment in using C11 _Generic to convert typenames to strings, |
| 3 | + * then use that to auto-generate arrays of type-names to pass to a function |
| 4 | + * along with a variadic argument list. |
| 5 | + * |
| 6 | + * That way the function can be called with many different argument lists |
| 7 | + * and type lists, e.g. for tracing/debug/logging purposes. |
| 8 | + * |
| 9 | + * I'm thinking of adapting the approach for use with systemtap SDTs, to |
| 10 | + * auto-generate data-type markers for the SDT tracepoints and insert them into |
| 11 | + * a suitable .stapsdt ELF section as const-data. Combined with stringifying |
| 12 | + * the argument names, that'd allow SDTs to be largely self-describing. |
| 13 | + * |
| 14 | + * It's not perfect since the _Generic can only understand a concrete list of |
| 15 | + * types. You can't pass any struct of your choice to it. It's also only going |
| 16 | + * to supply typenames, it doesn't appear possible to easily obtain any sort of |
| 17 | + * DWARF Die entry pointer or anything. |
| 18 | + * |
| 19 | + * An alternative might be to post-process the executable after compilation. |
| 20 | + * Generate the executable with -g3 so macro debuginfo is included. Read the |
| 21 | + * stap probes ELF section. Find the defining macros. Enumerate their |
| 22 | + * arguments, argument names (where simple variables) and argument types if |
| 23 | + * possible. Create a new section with the info and insert it into the |
| 24 | + * executable. |
| 25 | + */ |
| 26 | + |
| 27 | +#include <stdio.h> |
| 28 | +#include <string.h> |
| 29 | +#include <stdlib.h> |
| 30 | +#include <stdarg.h> |
| 31 | +#include <assert.h> |
| 32 | + |
| 33 | +#if __STDC_VERSION__ >= 201112L |
| 34 | + #define HAVE_TYPENAME |
| 35 | +#else |
| 36 | + #ifndef NO_WARN_MISSING_GENERIC |
| 37 | + #warning no c11 generic support available with STDC_VERSION = __STDC_VERSION__ |
| 38 | + #endif |
| 39 | +#endif |
| 40 | + |
| 41 | +struct typeformat { |
| 42 | + const char * const name; |
| 43 | + const char * const fmt; |
| 44 | +}; |
| 45 | + |
| 46 | +static const struct typeformat typeformats[] = { |
| 47 | + {"int", "%d"}, |
| 48 | + {"unsigned int", "%u"}, |
| 49 | + {"long", "%ld"}, |
| 50 | + {"unsigned long", "%lu"}, |
| 51 | + {"char", "%c"}, |
| 52 | + {"signed char", "%c"}, |
| 53 | + {"unsigned char", "%c"}, |
| 54 | + {"char *", "%s"}, |
| 55 | + {"const signed char", "%c"}, |
| 56 | + {"const unsigned char", "%c"}, |
| 57 | + {"const char *", "%s"}, |
| 58 | + {"signed const char *", "%s"}, |
| 59 | + {"unsigned const char *", "%s"}, |
| 60 | + {"const void *", "%p"} |
| 61 | +}; |
| 62 | + |
| 63 | +#ifdef HAVE_TYPENAME |
| 64 | +/* |
| 65 | + * Note on _Generic: |
| 66 | + * |
| 67 | + * C11 only |
| 68 | + * |
| 69 | + * A unique type may only appear once. |
| 70 | + * |
| 71 | + * const qualifiers are ignored for the variable type itself, but not for the |
| 72 | + * target type of pointer types. So these are the same: |
| 73 | + * |
| 74 | + * int |
| 75 | + * const int |
| 76 | + * |
| 77 | + * and these are the same: |
| 78 | + * |
| 79 | + * char * |
| 80 | + * char * const |
| 81 | + * |
| 82 | + * but these are distinct: |
| 83 | + * |
| 84 | + * char * |
| 85 | + * const char * |
| 86 | + * |
| 87 | + * Qualifier order is not significant, so these are the same: |
| 88 | + * |
| 89 | + * signed const char * |
| 90 | + * const signed char * |
| 91 | + * |
| 92 | + */ |
| 93 | + |
| 94 | +/* Map an expression to a type name */ |
| 95 | +#define TYPENAME(x) _Generic((x), \ |
| 96 | + int: "int", \ |
| 97 | + unsigned int: "unsigned int", \ |
| 98 | + long: "long", \ |
| 99 | + unsigned long: "unsigned long", \ |
| 100 | + char: "char", \ |
| 101 | + signed char: "signed char", \ |
| 102 | + unsigned char: "unsigned char", \ |
| 103 | + char *: "const char *", \ |
| 104 | + signed char *: "const signed char *", \ |
| 105 | + unsigned char *: "const unsigned char *", \ |
| 106 | + const char *: "const char *", \ |
| 107 | + const signed char *: "const signed char *", \ |
| 108 | + const unsigned char *: "const unsigned char *", \ |
| 109 | + void *: "void *", \ |
| 110 | + const void *: "void *", \ |
| 111 | + default: ("TYPENAME(" #x "): ERROR unhandled type") \ |
| 112 | + ) |
| 113 | +#else |
| 114 | +#define TYPENAME(x) "" |
| 115 | +#endif |
| 116 | + |
| 117 | +#define Expand1(x) x |
| 118 | +#define Expand(x) Expand1(x) |
| 119 | + |
| 120 | +/* Create the __auto_type declaration list */ |
| 121 | +#define _UNTYPED_ARG(x,n) const __auto_type arg ## n __attribute__((unused)) = x; |
| 122 | +#define UNTYPED_ARG1(x) _UNTYPED_ARG((x), 1) |
| 123 | +#define UNTYPED_ARG2(x,y) UNTYPED_ARG1(y); _UNTYPED_ARG((x),2) |
| 124 | +#define UNTYPED_ARG3(x,...) UNTYPED_ARG2(__VA_ARGS__); _UNTYPED_ARG((x),3) |
| 125 | +#define UNTYPED_ARG4(x,...) UNTYPED_ARG3(__VA_ARGS__); _UNTYPED_ARG((x),4) |
| 126 | +#define UNTYPED_ARG5(x,...) UNTYPED_ARG4(__VA_ARGS__); _UNTYPED_ARG((x),5) |
| 127 | +#define UNTYPED_ARG6(x,...) UNTYPED_ARG5(__VA_ARGS__); _UNTYPED_ARG((x),6) |
| 128 | +#define UNTYPED_ARG7(x,...) UNTYPED_ARG6(__VA_ARGS__); _UNTYPED_ARG((x),7) |
| 129 | +#define UNTYPED_ARG8(x,...) UNTYPED_ARG7(__VA_ARGS__); _UNTYPED_ARG((x),8) |
| 130 | +#define UNTYPED_ARG9(x,...) UNTYPED_ARG8(__VA_ARGS__); _UNTYPED_ARG((x),9) |
| 131 | +#define UNTYPED_ARG10(x,...) UNTYPED_ARG9(__VA_ARGS__); _UNTYPED_ARG((x),10) |
| 132 | + |
| 133 | +#define ARGTYPEVAR(n) argtype ## n |
| 134 | + |
| 135 | +/* |
| 136 | + * Create __auto_type declaration list with argtypeN constant definitions |
| 137 | + * containing string data type for each argN variable. |
| 138 | + */ |
| 139 | +#define _TYPED_ARG(x,n) \ |
| 140 | + _UNTYPED_ARG(x,n); \ |
| 141 | + const char * const Expand1(ARGTYPEVAR(n)) __attribute__((unused)) = TYPENAME(arg ## n) |
| 142 | +#define TYPED_ARG1(x) _TYPED_ARG((x), 1) |
| 143 | +#define TYPED_ARG2(x,y) TYPED_ARG1(y); _TYPED_ARG((x),2) |
| 144 | +#define TYPED_ARG3(x,...) TYPED_ARG2(__VA_ARGS__); _TYPED_ARG((x),3) |
| 145 | +#define TYPED_ARG4(x,...) TYPED_ARG3(__VA_ARGS__); _TYPED_ARG((x),4) |
| 146 | +#define TYPED_ARG5(x,...) TYPED_ARG4(__VA_ARGS__); _TYPED_ARG((x),5) |
| 147 | +#define TYPED_ARG6(x,...) TYPED_ARG5(__VA_ARGS__); _TYPED_ARG((x),6) |
| 148 | +#define TYPED_ARG7(x,...) TYPED_ARG6(__VA_ARGS__); _TYPED_ARG((x),7) |
| 149 | +#define TYPED_ARG8(x,...) TYPED_ARG7(__VA_ARGS__); _TYPED_ARG((x),8) |
| 150 | +#define TYPED_ARG9(x,...) TYPED_ARG8(__VA_ARGS__); _TYPED_ARG((x),9) |
| 151 | +#define TYPED_ARG10(x,...) TYPED_ARG9(__VA_ARGS__); _TYPED_ARG((x),10) |
| 152 | + |
| 153 | +/* |
| 154 | + * List of generated argument names from TYPED_ARGn. |
| 155 | + * |
| 156 | + * These are reversed on purpose. |
| 157 | + */ |
| 158 | +#define ARGNAMES1 arg1 |
| 159 | +#define ARGNAMES2 arg2, ARGNAMES1 |
| 160 | +#define ARGNAMES3 arg3, ARGNAMES2 |
| 161 | +#define ARGNAMES4 arg4, ARGNAMES3 |
| 162 | +#define ARGNAMES5 arg5, ARGNAMES4 |
| 163 | +#define ARGNAMES6 arg6, ARGNAMES5 |
| 164 | +#define ARGNAMES7 arg7, ARGNAMES6 |
| 165 | +#define ARGNAMES8 arg8, ARGNAMES7 |
| 166 | +#define ARGNAMES9 arg9, ARGNAMES8 |
| 167 | +#define ARGNAMES10 arg10, ARGNAMES9 |
| 168 | + |
| 169 | +/* |
| 170 | + * List of generated argument type-variable names from TYPED_ARGn. |
| 171 | + * |
| 172 | + * These are reversed on purpose. |
| 173 | + */ |
| 174 | +#define ARGTYPEVARS1 ARGTYPEVAR(1) |
| 175 | +#define ARGTYPEVARS2 ARGTYPEVAR(2), ARGTYPEVARS1 |
| 176 | +#define ARGTYPEVARS3 ARGTYPEVAR(3), ARGTYPEVARS2 |
| 177 | +#define ARGTYPEVARS4 ARGTYPEVAR(4), ARGTYPEVARS3 |
| 178 | +#define ARGTYPEVARS5 ARGTYPEVAR(5), ARGTYPEVARS4 |
| 179 | +#define ARGTYPEVARS6 ARGTYPEVAR(6), ARGTYPEVARS5 |
| 180 | +#define ARGTYPEVARS7 ARGTYPEVAR(7), ARGTYPEVARS6 |
| 181 | +#define ARGTYPEVARS8 ARGTYPEVAR(8), ARGTYPEVARS7 |
| 182 | +#define ARGTYPEVARS9 ARGTYPEVAR(9), ARGTYPEVARS8 |
| 183 | +#define ARGTYPEVARS10 ARGTYPEVAR(10), ARGTYPEVARS9 |
| 184 | + |
| 185 | +/* Create the __auto_type declaration list, and a static array of type names called _types */ |
| 186 | +#define _WRAPCALL_TYPES(n) const char * const _types[n+1] __attribute__((unused)) = { Expand1(ARGTYPEVARS ## n), 0 } |
| 187 | +#define WRAPCALL_TYPES1(...) TYPED_ARG1(__VA_ARGS__); _WRAPCALL_TYPES(1) |
| 188 | +#define WRAPCALL_TYPES2(...) TYPED_ARG2(__VA_ARGS__); _WRAPCALL_TYPES(2) |
| 189 | +#define WRAPCALL_TYPES3(...) TYPED_ARG3(__VA_ARGS__); _WRAPCALL_TYPES(3) |
| 190 | +#define WRAPCALL_TYPES4(...) TYPED_ARG4(__VA_ARGS__); _WRAPCALL_TYPES(4) |
| 191 | +#define WRAPCALL_TYPES5(...) TYPED_ARG5(__VA_ARGS__); _WRAPCALL_TYPES(5) |
| 192 | +#define WRAPCALL_TYPES6(...) TYPED_ARG6(__VA_ARGS__); _WRAPCALL_TYPES(6) |
| 193 | +#define WRAPCALL_TYPES7(...) TYPED_ARG7(__VA_ARGS__); _WRAPCALL_TYPES(7) |
| 194 | +#define WRAPCALL_TYPES8(...) TYPED_ARG8(__VA_ARGS__); _WRAPCALL_TYPES(8) |
| 195 | +#define WRAPCALL_TYPES9(...) TYPED_ARG9(__VA_ARGS__); _WRAPCALL_TYPES(9) |
| 196 | +#define WRAPCALL_TYPES10(...) TYPED_ARG10(__VA_ARGS__); _WRAPCALL_TYPES(10) |
| 197 | + |
| 198 | +#define WRAPCALL4(callfunc,...) do { WRAPCALL_TYPES4(__VA_ARGS__); callfunc(_types, 4, ARGNAMES4); } while (0) |
| 199 | +#define WRAPCALL4_TYPES(callfunc,_types,...) do { UNTYPED_ARG4(__VA_ARGS__); callfunc(_types, 4, ARGNAMES4); } while (0) |
| 200 | + |
| 201 | +#ifdef HAVE_TYPENAME |
| 202 | +#define CHECK_ARGTYPES4(expected_argtypes,...) do { WRAPCALL_TYPES4(__VA_ARGS__); compare_argtypes(expected_argtypes, _types, 4); } while (0) |
| 203 | +#else |
| 204 | +#define CHECK_ARGTYPES4(expected_argtypes,...) |
| 205 | +#endif |
| 206 | + |
| 207 | + |
| 208 | +/* |
| 209 | + * Call with |
| 210 | + * |
| 211 | + * int foo; |
| 212 | + * char * bar; |
| 213 | + * const char * const argtypes = { "int", "char *" }; |
| 214 | + * CHECK_ARGTYPES2(argtypes, foo, bar); |
| 215 | + * |
| 216 | + * The argtypes array is not counted. |
| 217 | + */ |
| 218 | +void |
| 219 | +compare_argtypes(const char * const * const explicit_types, const char * const * const detected_types, int nargs) |
| 220 | +{ |
| 221 | + int i = 0; |
| 222 | + const char * et; |
| 223 | + const char * dt; |
| 224 | + int error = 0; |
| 225 | + |
| 226 | + fputs("detected typenames:\n", stderr); |
| 227 | + while ((et = explicit_types[i]) && (dt = detected_types[i])) |
| 228 | + { |
| 229 | + if (i >= nargs) |
| 230 | + fprintf(stderr, "ERROR: wrong arg count %d\n", nargs); |
| 231 | + |
| 232 | + if (!dt != !et) |
| 233 | + { |
| 234 | + fprintf(stderr, " ERROR: detected_types[%d] and explicit_types[%d] end-marker mismatch: %p vs %p\n", |
| 235 | + i, i, dt, et); |
| 236 | + error++; |
| 237 | + } |
| 238 | + else if (strcmp(dt, et) != 0) |
| 239 | + { |
| 240 | + fprintf(stderr, " ERROR: detected_types[%d] and explicit_types[%d] differ: \"%s\" vs \"%s\"\n", |
| 241 | + i, i, dt, et); |
| 242 | + error++; |
| 243 | + } |
| 244 | + else |
| 245 | + { |
| 246 | + fprintf(stderr, " %s\n", dt); |
| 247 | + } |
| 248 | + |
| 249 | + i++; |
| 250 | + } while (et && dt); |
| 251 | + |
| 252 | + if (error) |
| 253 | + exit(1); |
| 254 | +} |
| 255 | + |
| 256 | +void |
| 257 | +format_args(const char * const * const types, int nargs, ...) |
| 258 | +{ |
| 259 | + va_list vl; |
| 260 | + int argno; |
| 261 | + const char *typename; |
| 262 | + char formatstr[200]; |
| 263 | + char *format_next = &formatstr[0]; |
| 264 | + |
| 265 | + memset(&formatstr[0], '\0', 200); |
| 266 | + for (argno = 0, typename = types[argno]; typename != 0; typename = types[++argno]) |
| 267 | + { |
| 268 | + if (argno > 0) { |
| 269 | + strcpy(format_next, ", "); |
| 270 | + format_next += 2; |
| 271 | + } |
| 272 | + if (typename[0] == '\0') { |
| 273 | + /* No type provided */ |
| 274 | + strcpy(format_next, "?"); |
| 275 | + format_next += 1; |
| 276 | + } |
| 277 | + else if (strcmp(typename, "int") == 0) { |
| 278 | + strcpy(format_next, "%d"); |
| 279 | + format_next += 2; |
| 280 | + } else if (strcmp(typename, "char *") == 0 || strcmp(typename, "const char *") == 0) { |
| 281 | + strcpy(format_next, "%s"); |
| 282 | + format_next += 2; |
| 283 | + } else if (strcmp(typename, "void *") == 0) { |
| 284 | + strcpy(format_next, "%p"); |
| 285 | + format_next += 2; |
| 286 | + } else { |
| 287 | + fprintf(stderr, "ERROR: format_char(): unhandled argtype \"%s\" at types[%d]\n", typename, argno); |
| 288 | + exit(1); |
| 289 | + } |
| 290 | + assert(argno <= nargs); |
| 291 | + } |
| 292 | + assert(argno == nargs); |
| 293 | + strcpy(format_next, "\n"); |
| 294 | + |
| 295 | + fprintf(stderr, "format string is %s", formatstr); |
| 296 | + |
| 297 | + va_start(vl, types); |
| 298 | + vprintf(&formatstr[0], vl); |
| 299 | + va_end(vl); |
| 300 | +} |
| 301 | + |
| 302 | +int main(void) { |
| 303 | + int a = 10; |
| 304 | + const char *b = "foo"; |
| 305 | + const int c = 9; |
| 306 | + void *d = (void*)0xDEADBEEFL; |
| 307 | + /* expected_argtypes should ignore const qualifiers */ |
| 308 | + const char * expected_argtypes[5] = {"int", "const char *", "int", "void *", 0}; |
| 309 | +#ifdef HAVE_TYPENAME |
| 310 | + static const int using_typeof = 1; |
| 311 | +#else |
| 312 | + static const int using_typeof = 0; |
| 313 | +#endif |
| 314 | + |
| 315 | + fprintf(stderr, "------- C standard: %ld, using typeof: %d ---------\n", |
| 316 | + (long int)(__STDC_VERSION__), using_typeof); |
| 317 | + |
| 318 | + /* Compare autodetected types to manually listed types */ |
| 319 | + CHECK_ARGTYPES4(expected_argtypes, a, b, c, d); |
| 320 | + /* Use autodetected types if supported */ |
| 321 | + WRAPCALL4(format_args, a, b, c, d); |
| 322 | + /* Pass with manually supplied type strings */ |
| 323 | + WRAPCALL4_TYPES(format_args, expected_argtypes, a, b, c, d); |
| 324 | + |
| 325 | + return 0; |
| 326 | +} |
0 commit comments