Here is a full-blown (user space) test program demonstrating the whole technique and how to use it. -hpa
#include <stddef.h> #include <string.h> #define _RET_IP_ ((unsigned long)__builtin_return_address(0)) #define noinline __attribute__((noinline)) #define used __attribute__((used)) /* __always_inline is defined in glibc already */ #define ifconst(x,y) __builtin_choose_expr(__builtin_constant_p(x),(x),(y)) static inline void notailcall(void) { asm volatile(""); } /* Change this to a null string to make all functions global */ #define STATIC static struct myputs_string { unsigned short len; char str[0]; }; STATIC int _myputs_struct(const struct myputs_string * const strs); STATIC int _myputs_string(const char *str); STATIC int __myputs(unsigned long ip, const char *str, size_t len); #if 1 #include <stdio.h> STATIC void dump_caller(unsigned long where) { const char *opname = NULL; const char *wheretoname = NULL; char ichar; unsigned long whereto = 0; #if defined(__i386__) || defined(__x86_64__) char opname_buf[4]; unsigned char opcode; where -= 5; opcode = *(unsigned char *)where; switch (opcode) { case 0xe8: opname = "call"; whereto = where + 5 + *(signed int *)(where + 1); break; case 0xe9: opname = "jmp"; whereto = where + 5 + *(signed int *)(where + 1); break; default: snprintf(opname_buf, sizeof opname_buf, "?%02x", opcode); opname = opname_buf; break; } #elif defined(__sparc__) const char regtype[4] = "gilo"; unsigned int opcode, op1, op3, ibit; signed int simm13, simm30; char opname_buf[32]; char *p; where -= 8; opcode = *(signed int *)where; op1 = opcode >> 30; op3 = (opcode >> 19) & 0x3f; ibit = (opcode >> 13) & 1; simm13 = (opcode & 0x1fff) << 2; simm30 = (opcode & 0x3fffffff) << 2; opname = opname_buf; if (op1 == 1) { opname = "call"; whereto = where + simm30; } else if (op1 == 2 && op3 == 0x38) { if (ibit) { snprintf(opname_buf, sizeof opname_buf, "jmpl %%%c%u %c 0x%x", regtype[(opcode >> 17) & 3], (opcode >> 14) & 7, simm13 < 0 ? '-' : '+', abs(simm13)); } else { snprintf(opname_buf, sizeof opname_buf, "jmpl %%%c%u + %%%c%u", regtype[(opcode >> 17) & 3], (opcode >> 14) & 7, regtype[(opcode >> 3) & 3], opcode & 7); } } else { snprintf(opname_buf, sizeof opname_buf, "?0x08x", opcode); } #else /* Unknown architecture */ #endif if (whereto == (unsigned long)_myputs_struct) { wheretoname = "_myputs_struct"; } else if (whereto == (unsigned long)_myputs_string) { wheretoname = "_myputs_string"; } else { wheretoname = "?"; } ichar = '['; if (opname) { printf("%c%p: %s", ichar, (void *)where, opname); ichar = ' '; } if (whereto) { printf("%c%p <%s>", ichar, (void *)whereto, wheretoname); ichar = ' '; } if (ichar != '[') putchar(']'); } STATIC int __myputs(unsigned long where, const char *str, size_t len) { size_t slen = strlen(str); size_t rv; len--; rv = printf("%p: \"%.*s\"%*s", (void *)where, (int)len, str, 16-(int)slen, ""); dump_caller(where); if (slen != len) printf(" <err: strlen = %zu, len = %zu>\n", slen, len); else printf(" <ok: len = %zu>\n", len); return rv; } STATIC int noinline _myputs_struct(const struct myputs_string * const strs) { return __myputs(_RET_IP_, strs->str, strs->len); } STATIC int noinline _myputs_string(const char *str) { return __myputs(_RET_IP_, str, strlen(str)+1); } #endif #define myputs(s) \ ({ \ int _rv; \ if (__builtin_constant_p(s) && \ __builtin_constant_p(strlen(s)) && \ strlen(s)+1 == sizeof(s) && \ sizeof(s) <= (size_t)65535) { \ static const struct { \ struct myputs_string _mps_hdr; \ char _mps_str[sizeof(s)]; \ } _mps = { \ ._mps_hdr.len = sizeof(s), \ ._mps_str = ifconst(s,""), \ }; \ _rv = _myputs_struct(&_mps._mps_hdr); \ } else { \ _rv = _myputs_string(s); \ } \ notailcall(); \ _rv; \ }) STATIC int test1(void); STATIC int test2(const char *strx); STATIC int test1(void) { return myputs("Foobar"); } STATIC int test2(const char *strx) { return myputs(strx); } int main(int argc, char *argv[]) { (void)argc; test1(); test2(argv[0]); return 0; }