< back

Macro abuse! or "Where have my function arguments gone?"

Finn O'Leary

2019-08-04

Early Saturday morning I was coding spotifyc when I had a strange error. A function was dropping my arguments in between a function call:

(gdb) bt #0 0x00007ffff7d88624 in mg_parse_uri (uri=..., scheme=0x7fffffffdc60, user_info=0x7ffff7f71208, host=0x7ffff7f711b2, port=0x7fffffffdc48, path=0x7ffff7fff680, query=0x7fffffffdc70, fragment=0x7fffffffdc80) at lib/mongoose/mongoose.c:5628 #1 0x00007ffff7d8fb0a in mg_connect_http_base (mgr=0x7fffffffde70, ev_handler=0x7ffff7f1793e <response_handler>, opts=..., scheme1=0x7ffff7f710d1 "http", scheme2=0x0, scheme_ssl1=0x7ffff7f710d6 "https", scheme_ssl2=0x0, url=0x0, path=0x7ffff7fff680, user_info=0x7ffff7f71208, host=0x7ffff7f711b2) at lib/mongoose/mongoose.c:8626 #2 0x00007ffff7f17fa6 in http_connect_v2 (mgr=0x7fffffffde70, ev_handler=0x7ffff7f1793e <response_handler>, method=0x7ffff7f711b2 "GET", url=0x7ffff7f71208 "https://accounts.spotify.com/authorize", query=0x7ffff7fff680 "client_id=..., extra_headers=0x0, body=0x0) at client.c:261

URL is clearly set when http_connect_v2 is called, but it seems to be NULL when mg_connect_http_base is called! Let's have a look at the calling line:

nc = mg_connect_http_base(mgr, MG_CB(ev_handler, user_data), opts, "http", NULL, "https", NULL, url, &path, &user, &host);

Well, we're passing it fine. Let's just check what MB_CB does:

#ifndef MG_ENABLE_CALLBACK_USERDATA #define MG_ENABLE_CALLBACK_USERDATA 0 #endif #if MG_ENABLE_CALLBACK_USERDATA #define MG_UD_ARG(ud) , ud #define MG_CB(cb, ud) cb, ud #else #define MG_UD_ARG(ud) #define MG_CB(cb, ud) cb #endif

Ok, we discard user_data under some circumstances. Could that be responsible? We don't seem to be defining MG_ENABLE_CALLBACK_USERDATA in this circumstance:

MONGOOSE_FLAGS= -g -DMG_ENABLE_SSL -DMG_ENABLE_THREADS \ -DMG_ENABLE_HTTP_WEBSOCKET=0"

How is it defined in this circumstance and is there a mismatch? Let's see if we can print the value of user_data in the function we're calling:

printf("user_data is %p\n", user_data); lib/mongoose/mongoose.c:8620:28: error: 'user_data' undeclared (first use in this function); did you mean 'user_info'?

Hmm, ok so that lines up with what you'd expect. Next let's have a look at the structure, maybe something freaky is happening with that.

struct mg_connect_opts { void *user_data; /* Initial value for connection's user_data */ unsigned int flags; /* Extra connection flags */ const char **error_string; /* Placeholder for the error string */ struct mg_iface *iface; /* Interface instance */ const char *nameserver; /* DNS server to use, NULL for default */

Well, that doesn't look too bad. But wait, there's more

#if MG_ENABLE_SSL

Uh oh

const char *ssl_cert; const char *ssl_key; const char *ssl_ca_cert; const char *ssl_cipher_suites; const char *ssl_server_name; const char *ssl_psk_identity; const char *ssl_psk_key; #endif };

Sidenote: You'll notice if you pull up the header that I removed the commments so I don't take up a whole page on a tiny structure.

Sigh, ok. What if we add MONGOOSE_FLAGS to our program's compile flags:

17:35 spotifyc$ ./bin/spotifyc auth url: https://accounts.spotify.com/authorize Please access 'https://accounts.spotify.com/login?continue=...' to continue authentication

It works, great!