提问人:zwol 提问时间:10/6/2023 更新时间:10/7/2023 访问量:111
出于测试目的强制“pthread_create”失败
Forcing `pthread_create` to fail for test purposes
问:
为了测试错误恢复,我想以可预测的方式使失败。最明显的方法是对特定进程中允许存在的线程数设置硬性上限。pthread_create
具体而言,在 Linux 上,会影响进程和线程创建;将限制设置为 1 将导致调用后任何代码都失败。Solaris 派生的 Unixes [我只测试了 OmniOS r151030] 没有RLIMIT_NPROC但确实有办法通过更高级的 setrctl
接口限制进程中的线程数。setrlimit(RLIMIT_NPROC)
pthread_create
setrlimit
我还没有找到一种方法在我尝试过的任何其他 Unix 上用 C 来做到这一点。特别是,这个问题底部的测试程序将在所有(AIX 7.1、FreeBSD 12.1、NetBSD 9.3、OpenBSD 6.8)上打印 “fail: pthread_create succeeded”。
所以,问题来了:请修改测试程序,使调用在一个或多个既不是 Linux 也不是 Solaris 的 Unix 上失败。在各种 Unix 上都有效的答案是首选(例如,如果你能在所有 BSD 衍生系统上展示一种方法来做到这一点,那就太好了)。pthread_create
注意:除了实际执行测试程序的进程之外,还会对进程产生副作用的答案(例如,对在特定 uid 下运行的线程总数或整个系统施加限制)对我没有好处。
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
static void *threadproc(void *arg)
{
puts("fail: pthread_create succeeded");
return arg;
}
int main(void)
{
struct rlimit r = { 1, 1 };
if (setrlimit(RLIMIT_NPROC, &r)) {
perror("error: setrlimit failed");
return 1;
}
pthread_t t;
int err = pthread_create(&t, 0, threadproc, 0);
if (err) {
printf("ok: pthread_create failed (%s)\n", strerror(err));
return 0;
}
err = pthread_join(t, 0);
if (err)
printf("error: pthread_join failed (%s)\n", strerror(err));
return 1;
}
答:
AIX的
AIX setrlimit()
具有RLIMIT_THREADS
:
RLIMIT_THREADS
每个进程可以创建的最大线程数。此限制由内核和 pthread 调试库强制执行。
只需更换
setrlimit(RLIMIT_NPROC, &r)
跟
setrlimit(RLIMIT_THREADS, &r)
(但是,我无权访问要测试的 AIX 实例)
BSD公司
BSD 似乎没有任何不依赖于每个用户或整个系统限制的等价物。
您可以使用POSIX功能保护堆栈内存int mprotect(void *addr, size_t len, int prot);
像这样的东西:
pthread_attr_t attr;
// attr initialization here:
int rc;
void *mystack;
size_t mystacksize = 2 * PTHREAD_STACK_MIN;
if ( pthread_attr_init(&attr) ) {
perror("pthread_attr_init()");
return -1;
}
// Get a big enough stack and align it on 4K boundary.
mystack = malloc(PTHREAD_STACK_MIN * 3);
if (!mystack) {
perror("Unable to acquire storage.");
return -1;
}
// Align stack, memory leak is possible
mystack = (((intptr_t)mystack) & 0xfff)
? (void*)( ( ((intptr_t)mystack) & ~0xfff) + 0x1000)
: mystack;
rc = pthread_attr_setstack(&attr, mystack, mystacksize);
if (rc != 0) {
perror("pthread_attr_setstack()");
return -1;
}
// Protect stack memory:
rc = memprotect(stack, mystacksize, PROT_NONE);
if (rc) {
perror("mprotect()");
return -1;
}
pthread_t t;
// Should fail, cause stack memory is protected
int err = pthread_create(&t, attr, threadproc, 0);
if (err) {
printf("ok: pthread_create failed (%s)\n", strerror(err));
return 0;
}
评论
pthread_attr_init()
不能保证在失败时返回负值:“成功完成后,应返回值 0;否则,应返回错误号以指示错误。pthread_attr_destroy()
pthread_attr_init()
pthread_create()
我们可以使用和捕获/修改系统调用ptrace
clone
PTRACE_SYSCALL
以下是针对 linux (x86_64) 的,但 BSD 具有类似但略有不同的选项。
在 的“进入系统调用”时,如果我们想强制失败,一种方法 [也许是最简单的] 是将 设置为零。这是系统调用号,0 是无效的系统调用。目标程序将出现错误。clone
orig_rax
ENOSYS
BSD(显然)必须使用略有不同的选项,但原理是相同的。
下面是跟踪程序 ():trace.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
//#include <syscall.h>
#include <asm/unistd.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
FILE *xflog;
int mode;
int maxthr;
int clone_count;
int kicked;
struct user_regs_struct regs;
#define outf(_fmt...) \
fprintf(xflog,_fmt)
#define REGSALL(_cmd) \
_cmd(r15) \
_cmd(r14) \
_cmd(r13) \
_cmd(r12) \
_cmd(rbp) \
_cmd(rbx) \
_cmd(r11) \
_cmd(r10) \
_cmd(r9) \
_cmd(r8) \
_cmd(rax) \
_cmd(rcx) \
_cmd(rdx) \
_cmd(rsi) \
_cmd(rdi) \
_cmd(orig_rax) \
_cmd(rip) \
_cmd(cs) \
_cmd(eflags) \
_cmd(rsp) \
_cmd(ss) \
_cmd(fs_base) \
_cmd(gs_base) \
_cmd(ds) \
_cmd(es) \
_cmd(fs) \
_cmd(gs)
#define GETREG(_reg) \
_getreg(#_reg,regs._reg);
#define MAXTHREADS 10
const char *svcname(int);
char svc_verbose[500] = {
[__NR_clone] = 1,
};
void *
func(void *ptr)
{
sleep(2);
return (void *) 0;
}
void
showstop(pid_t pid,int status)
{
outf("\n");
outf("trace: pid=%d status=%8.8X",pid,status);
do {
if (WIFSIGNALED(status)) {
outf(" WIFSIGNALED signo=%d",WTERMSIG(status));
break;
}
if (WIFSTOPPED(status)) {
outf(" WIFSTOPPED signo=%d",WSTOPSIG(status));
break;
}
if (WIFEXITED(status)) {
outf(" WIFEXITED code=%d",WEXITSTATUS(status));
break;
}
} while (0);
outf("\n");
}
void
_getreg(const char *sym,unsigned long long val)
{
outf("getreg: %s %16.16llX %llu\n",sym,val,val);
}
void
getregs(pid_t pid,int force)
{
int err = ptrace(PTRACE_GETREGS,pid,NULL,®s);
if (err < 0) {
outf("getregs: fail -- %s\n",strerror(errno));
exit(1);
}
long long svcno = regs.orig_rax;
long long retval = regs.rax;
outf("getregs: SYSCALL %d %s",svcno,svcname(svcno));
outf(" mode=%d/%s ret=%lld",mode,mode ? "exit" : "enter",retval);
if (retval < 0)
outf(" -- %s",strerror(-retval));
outf("\n");
int show = svc_verbose[svcno];
if (show || force) {
REGSALL(GETREG)
}
}
void
setregs(pid_t pid)
{
int err = ptrace(PTRACE_SETREGS,pid,NULL,®s);
if (err < 0) {
outf("setregs: fail -- %s\n",strerror(errno));
exit(1);
}
outf("setregs: mode=%d\n",mode);
getregs(pid,1);
kicked = 1;
}
int
main(int argc,char **argv)
{
--argc;
++argv;
xflog = fopen("LOG","w");
setlinebuf(xflog);
maxthr = -1;
for (; argc > 0; --argc, ++argv) {
char *cp = *argv;
if (*cp != '-')
break;
cp += 2;
switch (cp[-1]) {
case 'L':
maxthr = (*cp != 0) ? atoi(cp) : 1;
break;
}
}
if (argc < 1) {
outf("trace: need at least one arg\n");
exit(1);
}
outf("trace: __NR_clone=%d\n",__NR_clone);
int status;
pid_t pidtop = fork();
pid_t pidcur;
if (pidtop == 0) {
pidtop = getpid();
outf("trace: child pidtop=%d\n",pidtop);
ptrace(PTRACE_TRACEME,pidtop);
outf("trace: child execvp\n");
execvp(argv[0],argv);
outf("trace: execvp failure -- %s\n",strerror(errno));
exit(1);
}
outf("trace: parent pidtop=%d\n",pidtop);
pidcur = waitpid(pidtop,&status,0);
showstop(pidcur,status);
getregs(pidcur,1);
int err = ptrace(PTRACE_SYSCALL,pidcur,NULL,NULL);
if (err < 0) {
outf("trace: PTRACE_SYSCALL/1 fail pidcur=%d -- %s\n",
pidcur,strerror(errno));
//break;
}
while (1) {
pidcur = waitpid(-1,&status,0);
if (pidcur < 0)
break;
showstop(pidcur,status);
getregs(pidcur,kicked);
kicked = 0;
if (regs.orig_rax == __NR_clone) {
if (mode == 0) {
++clone_count;
// if we're over the limit, zap the syscall number to force
// target to see EINVAL
if ((maxthr >= 0) && (clone_count > maxthr)) {
regs.orig_rax = 0;
setregs(pidcur);
}
}
}
mode = ! mode;
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGSTOP)) {
}
int err = ptrace(PTRACE_SYSCALL,pidcur,NULL,NULL);
if (err < 0) {
outf("trace: PTRACE_SYSCALL/2 fail pidcur=%d -- %s\n",
pidcur,strerror(errno));
//break;
}
}
outf("trace: done\n");
fclose(xflog);
return 0;
}
#define NRALL(_cmd) \
_cmd(read,0) \
_cmd(write,1) \
_cmd(open,2) \
_cmd(close,3) \
_cmd(stat,4) \
_cmd(fstat,5) \
_cmd(lstat,6) \
_cmd(poll,7) \
_cmd(lseek,8) \
_cmd(mmap,9) \
_cmd(mprotect,10) \
_cmd(munmap,11) \
_cmd(brk,12) \
_cmd(rt_sigaction,13) \
_cmd(rt_sigprocmask,14) \
_cmd(rt_sigreturn,15) \
_cmd(ioctl,16) \
_cmd(pread64,17) \
_cmd(pwrite64,18) \
_cmd(readv,19) \
_cmd(writev,20) \
_cmd(access,21) \
_cmd(pipe,22) \
_cmd(select,23) \
_cmd(sched_yield,24) \
_cmd(mremap,25) \
_cmd(msync,26) \
_cmd(mincore,27) \
_cmd(madvise,28) \
_cmd(shmget,29) \
_cmd(shmat,30) \
_cmd(shmctl,31) \
_cmd(dup,32) \
_cmd(dup2,33) \
_cmd(pause,34) \
_cmd(nanosleep,35) \
_cmd(getitimer,36) \
_cmd(alarm,37) \
_cmd(setitimer,38) \
_cmd(getpid,39) \
_cmd(sendfile,40) \
_cmd(socket,41) \
_cmd(connect,42) \
_cmd(accept,43) \
_cmd(sendto,44) \
_cmd(recvfrom,45) \
_cmd(sendmsg,46) \
_cmd(recvmsg,47) \
_cmd(shutdown,48) \
_cmd(bind,49) \
_cmd(listen,50) \
_cmd(getsockname,51) \
_cmd(getpeername,52) \
_cmd(socketpair,53) \
_cmd(setsockopt,54) \
_cmd(getsockopt,55) \
_cmd(clone,56) \
_cmd(fork,57) \
_cmd(vfork,58) \
_cmd(execve,59) \
_cmd(exit,60) \
_cmd(wait4,61) \
_cmd(kill,62) \
_cmd(uname,63) \
_cmd(semget,64) \
_cmd(semop,65) \
_cmd(semctl,66) \
_cmd(shmdt,67) \
_cmd(msgget,68) \
_cmd(msgsnd,69) \
_cmd(msgrcv,70) \
_cmd(msgctl,71) \
_cmd(fcntl,72) \
_cmd(flock,73) \
_cmd(fsync,74) \
_cmd(fdatasync,75) \
_cmd(truncate,76) \
_cmd(ftruncate,77) \
_cmd(getdents,78) \
_cmd(getcwd,79) \
_cmd(chdir,80) \
_cmd(fchdir,81) \
_cmd(rename,82) \
_cmd(mkdir,83) \
_cmd(rmdir,84) \
_cmd(creat,85) \
_cmd(link,86) \
_cmd(unlink,87) \
_cmd(symlink,88) \
_cmd(readlink,89) \
_cmd(chmod,90) \
_cmd(fchmod,91) \
_cmd(chown,92) \
_cmd(fchown,93) \
_cmd(lchown,94) \
_cmd(umask,95) \
_cmd(gettimeofday,96) \
_cmd(getrlimit,97) \
_cmd(getrusage,98) \
_cmd(sysinfo,99) \
_cmd(times,100) \
_cmd(ptrace,101) \
_cmd(getuid,102) \
_cmd(syslog,103) \
_cmd(getgid,104) \
_cmd(setuid,105) \
_cmd(setgid,106) \
_cmd(geteuid,107) \
_cmd(getegid,108) \
_cmd(setpgid,109) \
_cmd(getppid,110) \
_cmd(getpgrp,111) \
_cmd(setsid,112) \
_cmd(setreuid,113) \
_cmd(setregid,114) \
_cmd(getgroups,115) \
_cmd(setgroups,116) \
_cmd(setresuid,117) \
_cmd(getresuid,118) \
_cmd(setresgid,119) \
_cmd(getresgid,120) \
_cmd(getpgid,121) \
_cmd(setfsuid,122) \
_cmd(setfsgid,123) \
_cmd(getsid,124) \
_cmd(capget,125) \
_cmd(capset,126) \
_cmd(rt_sigpending,127) \
_cmd(rt_sigtimedwait,128) \
_cmd(rt_sigqueueinfo,129) \
_cmd(rt_sigsuspend,130) \
_cmd(sigaltstack,131) \
_cmd(utime,132) \
_cmd(mknod,133) \
_cmd(uselib,134) \
_cmd(personality,135) \
_cmd(ustat,136) \
_cmd(statfs,137) \
_cmd(fstatfs,138) \
_cmd(sysfs,139) \
_cmd(getpriority,140) \
_cmd(setpriority,141) \
_cmd(sched_setparam,142) \
_cmd(sched_getparam,143) \
_cmd(sched_setscheduler,144) \
_cmd(sched_getscheduler,145) \
_cmd(sched_get_priority_max,146) \
_cmd(sched_get_priority_min,147) \
_cmd(sched_rr_get_interval,148) \
_cmd(mlock,149) \
_cmd(munlock,150) \
_cmd(mlockall,151) \
_cmd(munlockall,152) \
_cmd(vhangup,153) \
_cmd(modify_ldt,154) \
_cmd(pivot_root,155) \
_cmd(_sysctl,156) \
_cmd(prctl,157) \
_cmd(arch_prctl,158) \
_cmd(adjtimex,159) \
_cmd(setrlimit,160) \
_cmd(chroot,161) \
_cmd(sync,162) \
_cmd(acct,163) \
_cmd(settimeofday,164) \
_cmd(mount,165) \
_cmd(umount2,166) \
_cmd(swapon,167) \
_cmd(swapoff,168) \
_cmd(reboot,169) \
_cmd(sethostname,170) \
_cmd(setdomainname,171) \
_cmd(iopl,172) \
_cmd(ioperm,173) \
_cmd(create_module,174) \
_cmd(init_module,175) \
_cmd(delete_module,176) \
_cmd(get_kernel_syms,177) \
_cmd(query_module,178) \
_cmd(quotactl,179) \
_cmd(nfsservctl,180) \
_cmd(getpmsg,181) \
_cmd(putpmsg,182) \
_cmd(afs_syscall,183) \
_cmd(tuxcall,184) \
_cmd(security,185) \
_cmd(gettid,186) \
_cmd(readahead,187) \
_cmd(setxattr,188) \
_cmd(lsetxattr,189) \
_cmd(fsetxattr,190) \
_cmd(getxattr,191) \
_cmd(lgetxattr,192) \
_cmd(fgetxattr,193) \
_cmd(listxattr,194) \
_cmd(llistxattr,195) \
_cmd(flistxattr,196) \
_cmd(removexattr,197) \
_cmd(lremovexattr,198) \
_cmd(fremovexattr,199) \
_cmd(tkill,200) \
_cmd(time,201) \
_cmd(futex,202) \
_cmd(sched_setaffinity,203) \
_cmd(sched_getaffinity,204) \
_cmd(set_thread_area,205) \
_cmd(io_setup,206) \
_cmd(io_destroy,207) \
_cmd(io_getevents,208) \
_cmd(io_submit,209) \
_cmd(io_cancel,210) \
_cmd(get_thread_area,211) \
_cmd(lookup_dcookie,212) \
_cmd(epoll_create,213) \
_cmd(epoll_ctl_old,214) \
_cmd(epoll_wait_old,215) \
_cmd(remap_file_pages,216) \
_cmd(getdents64,217) \
_cmd(set_tid_address,218) \
_cmd(restart_syscall,219) \
_cmd(semtimedop,220) \
_cmd(fadvise64,221) \
_cmd(timer_create,222) \
_cmd(timer_settime,223) \
_cmd(timer_gettime,224) \
_cmd(timer_getoverrun,225) \
_cmd(timer_delete,226) \
_cmd(clock_settime,227) \
_cmd(clock_gettime,228) \
_cmd(clock_getres,229) \
_cmd(clock_nanosleep,230) \
_cmd(exit_group,231) \
_cmd(epoll_wait,232) \
_cmd(epoll_ctl,233) \
_cmd(tgkill,234) \
_cmd(utimes,235) \
_cmd(vserver,236) \
_cmd(mbind,237) \
_cmd(set_mempolicy,238) \
_cmd(get_mempolicy,239) \
_cmd(mq_open,240) \
_cmd(mq_unlink,241) \
_cmd(mq_timedsend,242) \
_cmd(mq_timedreceive,243) \
_cmd(mq_notify,244) \
_cmd(mq_getsetattr,245) \
_cmd(kexec_load,246) \
_cmd(waitid,247) \
_cmd(add_key,248) \
_cmd(request_key,249) \
_cmd(keyctl,250) \
_cmd(ioprio_set,251) \
_cmd(ioprio_get,252) \
_cmd(inotify_init,253) \
_cmd(inotify_add_watch,254) \
_cmd(inotify_rm_watch,255) \
_cmd(migrate_pages,256) \
_cmd(openat,257) \
_cmd(mkdirat,258) \
_cmd(mknodat,259) \
_cmd(fchownat,260) \
_cmd(futimesat,261) \
_cmd(newfstatat,262) \
_cmd(unlinkat,263) \
_cmd(renameat,264) \
_cmd(linkat,265) \
_cmd(symlinkat,266) \
_cmd(readlinkat,267) \
_cmd(fchmodat,268) \
_cmd(faccessat,269) \
_cmd(pselect6,270) \
_cmd(ppoll,271) \
_cmd(unshare,272) \
_cmd(set_robust_list,273) \
_cmd(get_robust_list,274) \
_cmd(splice,275) \
_cmd(tee,276) \
_cmd(sync_file_range,277) \
_cmd(vmsplice,278) \
_cmd(move_pages,279) \
_cmd(utimensat,280) \
_cmd(epoll_pwait,281) \
_cmd(signalfd,282) \
_cmd(timerfd_create,283) \
_cmd(eventfd,284) \
_cmd(fallocate,285) \
_cmd(timerfd_settime,286) \
_cmd(timerfd_gettime,287) \
_cmd(accept4,288) \
_cmd(signalfd4,289) \
_cmd(eventfd2,290) \
_cmd(epoll_create1,291) \
_cmd(dup3,292) \
_cmd(pipe2,293) \
_cmd(inotify_init1,294) \
_cmd(preadv,295) \
_cmd(pwritev,296) \
_cmd(rt_tgsigqueueinfo,297) \
_cmd(perf_event_open,298) \
_cmd(recvmmsg,299) \
_cmd(fanotify_init,300) \
_cmd(fanotify_mark,301) \
_cmd(prlimit64,302) \
_cmd(name_to_handle_at,303) \
_cmd(open_by_handle_at,304) \
_cmd(clock_adjtime,305) \
_cmd(syncfs,306) \
_cmd(sendmmsg,307) \
_cmd(setns,308) \
_cmd(getcpu,309) \
_cmd(process_vm_readv,310) \
_cmd(process_vm_writev,311) \
_cmd(kcmp,312) \
_cmd(finit_module,313) \
_cmd(sched_setattr,314) \
_cmd(sched_getattr,315) \
_cmd(renameat2,316) \
_cmd(seccomp,317) \
_cmd(getrandom,318) \
_cmd(memfd_create,319) \
_cmd(kexec_file_load,320) \
_cmd(bpf,321) \
_cmd(execveat,322) \
_cmd(userfaultfd,323) \
_cmd(membarrier,324) \
_cmd(mlock2,325) \
_cmd(copy_file_range,326) \
_cmd(preadv2,327) \
_cmd(pwritev2,328) \
_cmd(pkey_mprotect,329) \
_cmd(pkey_alloc,330) \
_cmd(pkey_free,331) \
_cmd(statx,332) \
_cmd(io_pgetevents,333) \
_cmd(rseq,334) \
_cmd(pidfd_send_signal,424) \
_cmd(io_uring_setup,425) \
_cmd(io_uring_enter,426) \
_cmd(io_uring_register,427) \
_cmd(open_tree,428) \
_cmd(move_mount,429) \
_cmd(fsopen,430) \
_cmd(fsconfig,431) \
_cmd(fsmount,432) \
_cmd(fspick,433) \
_cmd(pidfd_open,434) \
_cmd(clone3,435)
#define SVCDEF(_sym,_val) \
[__NR_ ## _sym] = #_sym,
const char *svcdef[500] = {
NRALL(SVCDEF)
};
const char *
svcname(int svcno)
{
return svcdef[svcno];
}
这是目标/测试程序():target.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#define MAXTHREADS 5
void *
func(void *ptr)
{
sleep(2);
return (void *) 0;
}
int
main(void)
{
pthread_t tid[MAXTHREADS];
int err;
int count = 0;
for (int idx = 0; idx < MAXTHREADS; ++idx, ++count) {
err = pthread_create(&tid[idx],NULL,func,NULL);
if (err != 0) {
printf("main: idx=%d -- %s\n",idx,strerror(err));
break;
}
printf("main: start idx=%d\n",idx);
}
for (int idx = 0; idx < count; ++idx) {
pthread_join(tid[idx],NULL);
printf("main: joined idx=%d\n",idx);
}
printf("main: done\n");
return 0;
}
编译时使用:
cc -o trace trace.c
cc -o target target.c -lpthread
这是正常输出 ():./target
main: start idx=0
main: start idx=1
main: start idx=2
main: start idx=3
main: start idx=4
main: joined idx=0
main: joined idx=1
main: joined idx=2
main: joined idx=3
main: joined idx=4
main: done
以下是 的输出。请注意,跟踪程序将调试/跟踪输出放在文件中:./trace -L2 ./target
./LOG
main: start idx=0
main: start idx=1
main: idx=2 -- Bad file descriptor
main: joined idx=0
main: joined idx=1
main: done
评论
pthread_create