简体   繁体   中英

Is there a way to dump a C struct?

I've written a program to probe the limits of a system's C time.h functions and dump them out in JSON. Then other things which depend on those functions can know their limits.

# system time.h limits, as JSON 
{
  "gmtime": {
    "max": 2147483647,
    "min": -2147483648
  },
  "localtime": {
    "max": 2147483647,
    "min": -2147483648
  },
  "mktime": {
    "max": {
      "tm_sec": 7,
      "tm_min": 14,
      "tm_hour": 19,
      "tm_mday": 18,
      "tm_mon": 0,
      "tm_year": 138,
      "tm_wday": 1,
      "tm_yday": 17,
      "tm_isdst": 0
    },
    "min": {
      "tm_sec": 52,
      "tm_min": 45,
      "tm_hour": 12,
      "tm_mday": 13,
      "tm_mon": 11,
      "tm_year": 1,
      "tm_wday": 5,
      "tm_yday": 346,
      "tm_isdst": 0
    }
  }
}

gmtime() and localtime() are simple enough, they just take numbers, but mktime() takes a tm struct. I wrote a custom function to turn a tm struct into a JSON hash.

/* Dump a tm struct as a json fragment */
char * tm_as_json(const struct tm* date) {
    char *date_json = malloc(sizeof(char) * 512);
#ifdef HAS_TM_TM_ZONE
    char zone_json[32];
#endif
#ifdef HAS_TM_TM_GMTOFF
    char gmtoff_json[32];
#endif

    sprintf(date_json,
            "\"tm_sec\": %d, \"tm_min\": %d, \"tm_hour\": %d, \"tm_mday\": %d, \"tm_mon\": %d, \"tm_year\": %d, \"tm_wday\": %d, \"tm_yday\": %d, \"tm_isdst\": %d",
            date->tm_sec, date->tm_min, date->tm_hour, date->tm_mday,
            date->tm_mon, date->tm_year, date->tm_wday, date->tm_yday, date->tm_isdst
    );

#ifdef HAS_TM_TM_ZONE
    sprintf(&zone_json, ", \"tm_zone\": %s", date->tm_zone);
    strcat(date_json, zone_json);
#endif
#ifdef HAS_TM_TM_GMTOFF
    sprintf(&gmtoff_json", \"tm_gmtoff\": %ld", date->tm_gmtoff);
    strcat(date_json, gmtoff_json);
#endif

    return date_json;
}

Is there a way to do this generically, for any given struct?

Note: C, not C++.

Not in C—at least in general. But if the C module is compiled with debug symbols, and the object module is available, you could parse that and discover everything about the structure. I bet there's a library for your system to assist with that.

Having come across the same issue, i wrote one myself. https://github.com/jamie-pate/jstruct . It's written to allow annotating existing c structures, then generate meta-data information based on the annotations. The library reads the metadata to import/export c structures to json strings and back. A python script takes care of parsing the annotated header and generating new headers and metadata initializers. The jstruct library uses https://github.com/json-c/json-c internally. I have also noticed https://github.com/marel-keytech ... but that was after writing the entire thing. (and info on that project's page is sparse)

There's no support yet for annotating a single struct from an existing system lib but you could wrap the tm struct in an annotated custom struct. (I think?)

Feel free to add features requests or even pull requests if you have ideas that would make the library more useful to you. One idea I had would be to add a way to embed that kind of read only struct inside an annotated wrapper with a @inline annotation: eg (currently unsupported but could be added)

//@json
struct my_time {
    //@inline
    struct tm tm;
}

Code:

struct my_time t;

mktime(&t.tm);
struct json_object *result = jstruct_export(t, my_time);

In the mean time you could do the same thing without @inline (since it hasn't been written yet) and just extract the tm property by hand with json_object_to_json_string(json_object_object_get(result, "tm"))

This won't quite give you what you're asking for, but it might help a little:

#define NAME_AND_INT(buf, obj, param) \
        sprintf((buf), "\"%s\": %d, ", #param, (obj)->(param))

You could then iterate, eg something like (note: not tested; consider this pseudo-code):

char * tm_as_json(const struct tm* date) {
    /* ... */
    char buf[BUFSIZ]; /* or, use your date_json */

    pos = buf; /* I note you use the equivalent of &buf -- that works too */
               /* (not sure which is "better", but I've always left the & off
                * things like that -- buf is essentially a pointer, it's just
                * been allocated in a different way.  At least that's how I
                * think of it. */
    pos += NAME_AND_INT(pos, date, tm_sec);
    pos += NAME_AND_INT(pos, date, tm_min);
    /* ... more like this ... */

    /* strip trailing ", " (comma-space): */
    pos-=2;
    *pos = '\0';

    /* ... */
}

You could similarly define NAME_AND_STRING , NAME_AND_LONG , etc. (for tm_zone and tm_gmtoff) as needed.

Again, it's not a generic solution, but it at least gets you a little closer, maybe.

Disclaimer : I'm the owner of the project https://github.com/tamask1s/zax-parser

With the help of the library, you can convert a C struct to JSON if you provide some information on your struct members which needs to be converted. There will be no need to include generated code in your project, but you will need a c++11 compiler in order to use it.

The lib is quite immature because I have implemented only the features I needed, but you may extend it, or you may use it as inspiration.

Example:

#define some_json_properties JSON_PROPERTY(x), JSON_PROPERTY(s) 
 
struct some_class 
{ 
    int x = 9; 
    std::string s = "something";  
 
    ZAX_JSON_SERIALIZABLE(some_class, some_json_properties) 
}; 
 
std::string some_json = some_obj; 

---some_json's value:---

{"x":9, "s":"something"} 

Nesting of objects is also possible, please check this example: https://tamask1s.github.io/zax-parser/index.html#Parsing_of_structures_with_fields_of_serializable_structures

Tom Christiansen once wrote pstruct/h2ph which is in perl CORE to parse .stabs info from the used compiler, and create readable info for all data structures.

C structs into JSON is trivial based on h2ph. http://perl5.git.perl.org/perl.git/blob/HEAD:/utils/h2ph.PL

This macro does not do exactly what you want (generate JSON dump of C data), but I think it shows some possibility. You can dump content of any C data with a "p(...);"call.

I used gdb as external helper to make this work, but it is possible to implement one with libbfd. In that case, you can fully control your output - like generating JSON compatible output.

#ifndef PP_H
#define PP_H
/*
* Helper function (macro) for people who loves printf-debugging.
* This dumps content of any C data/structure/expression without prior
* knowledge of actual format. Works just like "p" or "pp" in Ruby.
*
* Usage:
* p(anyexpr);
*
* NOTE:
* - Program should be compiled with "-g" and preferrably, with "-O0".
*
* FIXME:
* - Would be better if this doesn't depend on external debugger to run.
* - Needs improvement on a way prevent variable from being optimized away.
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>

// Counts number of actual arguments.
#define COUNT_(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define COUNT(...) COUNT_(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)

// Dispatches macro call by number of actual arguments.
// Following is an example of actual macro expansion performed in case
// of 3 arguments:
//
// p(a, b, c)
// -> FUNC_N(p, COUNT(a, b, c), a, b, c)
// -> FUNC_N(p, 3, a, b, c)
// -> p_3(a, b, c)
//
// This means calling with simple "p(...)" is fine for any number of
// arguments, simulating "true" variadic macro.
#define CONCAT(name, count) name##count
#define FUNC_N(name, count, ...) CONCAT(name, count)(__VA_ARGS__)

// Forbids variable from being optimized out, so debugger can access it.
//
// FIXME:
// - Current implementation does not work with certain type of symbols
#define ENSURE(...) FUNC_N(ENSURE_, COUNT(__VA_ARGS__), __VA_ARGS__)
#define ENSURE_1(a) asm(""::"m"(a))
#define ENSURE_2(a, ...) do { ENSURE_1(a); ENSURE_1(__VA_ARGS__); } while (0)
#define ENSURE_3(a, ...) do { ENSURE_1(a); ENSURE_2(__VA_ARGS__); } while (0)
#define ENSURE_4(a, ...) do { ENSURE_1(a); ENSURE_3(__VA_ARGS__); } while (0)
#define ENSURE_5(a, ...) do { ENSURE_1(a); ENSURE_4(__VA_ARGS__); } while (0)
#define ENSURE_6(a, ...) do { ENSURE_1(a); ENSURE_5(__VA_ARGS__); } while (0)
#define ENSURE_7(a, ...) do { ENSURE_1(a); ENSURE_6(__VA_ARGS__); } while (0)
#define ENSURE_8(a, ...) do { ENSURE_1(a); ENSURE_7(__VA_ARGS__); } while (0)

// Dumps content of given symbol (uses external GDB for now)
//
// NOTE:
// - Should use libbfd instead of gdb? (but this adds complexity...)
#define PP_D(...) do { \
char *arg[] = { __VA_ARGS__, NULL }; \
char **argp = arg; \
char cmd[1024]; \
FILE *tmp = tmpfile(); \
fprintf(tmp, "attach %d\n", getpid()); \
fprintf(tmp, "frame 2\n"); \
while (*argp) \
fprintf(tmp, "p %s\n", *argp++); \
fprintf(tmp, "detach\n"); \
fflush(tmp); \
sprintf(cmd, "gdb -batch -x /proc/%d/fd/%d", \
getpid(), fileno(tmp)); \
system(cmd); \
fclose(tmp); \
} while (0)

#define PP(...) do { \
FUNC_N(PP_, COUNT(__VA_ARGS__), __VA_ARGS__); \
ENSURE(__VA_ARGS__); \
} while (0)
#define PP_1(a) do { PP_D(#a); } while (0)
#define PP_2(a,b) do { PP_D(#a,#b); } while (0)
#define PP_3(a,b,c) do { PP_D(#a,#b,#c); } while (0)
#define PP_4(a,b,c,d) do { PP_D(#a,#b,#c,#d); } while (0)
#define PP_5(a,b,c,d,e) do { PP_D(#a,#b,#c,#d,#e); } while (0)
#define PP_6(a,b,c,d,e,f) do { PP_D(#a,#b,#c,#d,#e,#f); } while (0)
#define PP_7(a,b,c,d,e,f,g) do { PP_D(#a,#b,#c,#d,#e,#f,#g); } while (0)
#define PP_8(a,b,c,d,e,f,g,h) do { PP_D(#a,#b,#c,#d,#e,#f,#g,#h); } while (0)

// Comment this out if you think this is too aggressive.
#define p PP

#endif

Indentation is lost in above paste, but you can grab the source from: https://github.com/tai/ruby-p-for-c

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM