Is there a way to flag the use of non-reentrant C library calls?
Asked Answered
W

5

12

I'm working on a project that's heavily multi-threaded, and was wondering if there's a way to have the compiler flag the use of non-reentrant calls to the C library (e.g. strtok intsead of strtok_r)? If not, is there a list of calls that are non-reentrant so I can grep through my code base periodically?

A related question is if there's a way to flag 3d party library use of non-reentrant calls.

I'm assuming reentrancy implies thread-safety, but not necessarily the other way around. Is there a good reason to use non-reentrant calls in a threaded project?

Wirewove answered 24/6, 2011 at 1:25 Comment(2)
Question - are all C runtime calls in Linux that maintain state between calls (e.g. malloc, rand, strtok, etc...) inherently non-threadsafe? Or is there a compiler/linker directive to specify linking with a thread safe version for these calls? I'm actually wondering if there's really a problem to be solved for thread safety.Guillemot
@selbie: No, it varies. For instance, malloc is generally threadsafe. The threadsafety of rand() is a philosophical question anyway. A perfect rand implementation (an oracle) would inherently be threadsafe.Cubature
H
5

For source, you could possibly insist that every source file contains the line:

#include <beware.h>

after the C headers, and then the beware.h header file contains:

#define strtok   unsafe_function_call_detected_strtok
#define getenv   unsafe_function_call_detected_getenv

or some other suitable set of names that are unlikely to be real functions. That will result in compilation and/or linker errors.

For libraries, it's a bit more difficult. You can look into using nm to extract all the unresolved names in each object file and ensure none of the unsafe ones are called.

This wouldn't be the compiler doing it but it would be easy enough to incorporate into the build scripts. See the following transcript:

$ cat qq.c
    #include <stdio.h>

    int main (int argc, char *argv[]) {
        printf ("Hello, world.\n");
        return 0;
    }

$ gcc -c -o qq.o qq.c

$ nm qq.o
00000000 b .bss
00000000 d .data
00000000 r .rdata
00000000 t .text
         U ___main
00000000 T _main
         U _puts

You can see the unresolved symbols in that output with a U marker (and gcc has very sneakily decided to use puts instead of printf since I gave it a constant string with no formatting commands).

Hiles answered 24/6, 2011 at 1:34 Comment(9)
Good idea on the header file and nm script. Is there a list of non-reentrant C lib functions somewhere?Wirewove
If you're using GCC, you can use #pragma GCC poison strtok as a better alternative to the #define.Readily
As a variant on the nm style you can define a file like this: checkfns.c:void not_thread_safe(); void puts() { not_thread_safe(); } Then it will hide the system versions of the functions and you'll get an error like: Undefined symbols: "_not_thread_safe", referenced from: _puts in checkfns.o if you try to use puts. (You'll need to enable dead code stripping for cases that don't use it though ... -Xlinker -dead_strip for me)Immoderacy
If you use the #define, use a name like unsafe_function_call_detected, so that the compiler error messages include something that tell you what's actually wrong.Libava
@Karl: isn't ooga-booga-wtf clear enough? :-) No, seriously, that's a good suggestion, I'll update the answer.Hiles
@Michael, your idea is interesting, but I can't get it to work. Let me know if it works for you. I'm using GCC 4.5.2.Wirewove
@suravi It certainly works for me .. I've put the code I use here: gist.github.com/1044114 - I'm on OS X using gcc 4.0.1Immoderacy
@suravi Looks like the linker option to perform dead function stripping is different on your platform (thats the -dead_strip option) You'll need to change that to whatever your platform uses. (man gcc or man ld shoudl find it for you)Immoderacy
Thanks, I'll look for an equivalent option on Ubuntu.Wirewove
W
4

is there a list of calls that are non-reentrant so I can grep through my code base periodically?

I looked through the GNU libc function list, and picked out the ones with _r. Here's the list.

asctime, crypt, ctime, drand48, ecvt, encrypt, erand48, fcvt, fgetgrent, fgetpwent, getdate, getgrent, getgrgid, getgrnam, gethostbyaddr, gethostbyname2, gethostbyname, getmntent, getnetgrent, getpwent, getpwnam, getpwuid, getutent, getutid, getutline, gmtime, hcreate, hdestroy, hsearch, initstate, jrand48, lcong48, lgamma, lgammaf, lgammal, localtime, lrand48, mrand48, nrand48, ptsname, qecvt, qfcvt, rand, random, readdir64, readdir, seed48, setkey, setstate, srand48, srandom, strerror, strtok, tmpnam, ttyname

Wirewove answered 26/6, 2011 at 15:41 Comment(1)
Note on readdir_r: check out the manpage. GNU libc recommends to use the regular readdir and deprecate readdir_rChefoo
I
1

Addressing the second part of your question:

Non re-entrant calls may be implemented in a way that gives them a performance advantage. In this case if you know you're only making those calls from one thread (or within one critical section), and they're your bottleneck then choosing the non-reentrant call makes sense. But I'd only do it if there were performance measurements suggesting that it was critical to do so... And carefully document it..

Immoderacy answered 24/6, 2011 at 1:37 Comment(0)
S
0

For binaries, you can use LD_PRELOAD to intercept whatever C library functions you like and take whatever action you want (abort, log an error but proceed, etc.)

During development, you can also use valgrind to do the same.

For some sample code and references, see the answers to how could I intercept linux sys calls?

Surculose answered 24/6, 2011 at 4:34 Comment(0)
H
0

Cppcheck will flag use of non-reentrant standard library functions. Enable the portability warnings to enable this check.

Refer to non_reentrant_functions_list in checknonreentrantfunctions.h for a list of the functions Cppcheck will flag.

Example of the message Cppcheck will emit:

Non reentrant function 'strtok' called. For threadsafe applications it is recommended to use the reentrant replacement function 'strtok_r'. (portability: nonreentrantFunctionsstrtok)

Herriot answered 8/10, 2014 at 18:4 Comment(1)
The list of functions that are checked appears incomplete. strerror_r() is missing. There are also other functions (without _r suffix) that are not reentrant, such as getenv(), system(), and whole bunch of others. List here: [pubs.opengroup.org/onlinepubs/9699919799/functions/…. But even that isn't complete. Think about setlocale() or any other function that modifies a process-wide attribute. You can't call those from threaded libraries without potentially causing havoc.Regenerative

© 2022 - 2024 — McMap. All rights reserved.