最近更新
最近更新

strace工具的實作原理ptrace系統調用strace工具的實作原理

strace是Linux系統下的一個用來跟蹤系統調用的工具,它的實作基礎是ptrace系統調用。使用strace工具可以跟蹤一個程式執行過程中發生的系統調用。

我這裡講到的内容有一點點和mips體系相關,不過不熟悉mips也不影響閱讀。

ptrace系統調用

ptrace系統調用提供了一種方法來跟蹤和控制程序的執行,它可以讀取和修改程序位址空間中的内容,包括寄存器的值。ptrace主要用于實作斷點調試和跟蹤系統調用。該系統調用的原型如下:

long ptrace(enum __ptrace_request request, pid_t pid, void *addr,void *data);

ptrace的四個參數的含義為:

1.   request:用于選擇一個操作,見下文。

2.   pid:目标程序即被跟蹤程序的pid。

3.   addr和data用于修改和拷貝被跟蹤程序的程序位址空間的資料。

下面的内容中将用父程序指代跟蹤者,用子程序指代被跟蹤者。實際上,在一個程序被跟蹤之後,跟蹤者程序會在某種意義上充當被跟蹤程序的父程序(如使用ps指令就可以看到他們的父子關系),而子程序真正的父程序被儲存在其task_struct結構的real_parent成員中。

使用ptrace跟蹤程序

父程序跟蹤一個程序的方式有兩種:1.調用fork(),然後子程序打上PTRACE_TRACEME标記,并執行exec。2.父程序可以給自己打上PTRACE_ATTACH标記來跟蹤一個已有程序。

一個程序被跟蹤後,他隻要接收到一個信号(即使這個信号被設定為忽略)就會停止運作(SIGKILL除外),然後父程序會在每次調用wait()時得到子程序停止運作的通知,這時父程序就可以檢測和修改子程序了,随後父程序可以讓子程序繼續運作。

當父程序不想跟蹤了,可以通過設定PTRACE_KILL标記來終止子程序的運作。也可以通過設定PTRACE_DETACH标記讓子程序解除被跟蹤,繼續正常運作。

常用的request

PTRACE_TRACEME

程序設定這個request目的是讓自己被父程序跟蹤。任何發送到該子程序的信号(除了SIGKILL)都會導緻他停下來,并在父程序wait()的時候通知到父程序。另外,子程序後續調用exec會導緻子程序自己收到一個SIGTRAP信号,這是為了讓父程序有機會在exec的新程式開始執行前獲得控制權。

除非一個程序知道父程序要跟蹤他,一般不會去設定這個request。設定這個請求時,pid,addr和data三個參數都會被忽略。

這個request隻供子程序設定,其他的request都是隻供父程序使用的。相應的,下面的request中,參數pid為被跟蹤的子程序的pid。

PTRACE_ATTACH

将pid指定的程序作為自己要跟蹤的程序,并開始跟蹤。這和子程序自己調用PTRACE_TRACEME的效果相同。

設定這個request時,子程序會首先收到一個SIGSTOP信号,但并不會停止,隻是導緻跟蹤者程序第一次被中斷,進而開始跟蹤,否則隻能等到子程序接收到第一個信号時才開始跟蹤。之後當子程序有待決信号時,程序總是會暫停,這時父程序通過SIGCHLD信号得到通知。在子程序暫停前,父程序可使用wait函數等待。

參數addr和data會被忽略。

PTRACE_CONT

讓被停掉的子程序繼續運作,而當子程序再次接收到信号時會暫停。

參數data如果被設定為一個非零值并且不是SIGSTOP,那data就是父程序發送給子程序的信号,否則,不給子程序發送信号。這樣一來,父程序可以控制是否向子程序發送一個信号。

參數addr會被忽略。

PTRACE_SYSCALL

讓停止的子程序繼續運作(同PTRACE_CONT),而當子程序再次接收到信号時會暫停,另外,在子程序中發生系統調用時,在系統調用的入口和結束時子程序也會停止,這時父程序認為子程序是因為收到SIGTRAP信号而停止的。

由于子程序在系統調用的入口和結束時都會停止,父程序就可以在系統調用入口處停止後,獲得系統調用的參數資訊,而在系統調用結束時停止後,獲得系統調用的傳回值。

參數addr會被忽略。

PTRACE_DETACH

讓停止的子程序繼續運作(同PTRACE_CONT),但是在這之前會先與跟蹤它的父程序解除PTRACE_ATTACH或PTRACE_TRACEME時的關系。

參數addr會被忽略。

PTRACE_KILL

給子程序發送一個SIGKILL信号來終止子程序。addr和data參數會被忽略。

PTRACE_PEEKTEXT,PTRACE_PEEKDATA

讀取程序位址空間中addr位址處的内容,讀出的長度為一個word(4位元組),作為ptrace()的傳回值(long型)傳回。Linux中的代碼和資料的位址空間并不是分離的,是以這兩個request實際上意義相同。

參數data會被忽略。

PTRACE_PEEKUSR

在程序的USER區域讀取一個word的長度。參數addr是指相對USER開頭的offset,結果作為傳回值。參數data會被忽略。

在mips中的程序自身資訊和程序位址空間中并沒有所謂的USER區域,在核心中通過參數addr的值,判斷應該傳回什麼結果,如下:

/* Read the word at location addr in theUSER area. */
    case PTRACE_PEEKUSR: {
       struct pt_regs *regs;
       unsigned long tmp = 0;
 
       /* 獲得程序位址空間的pt_regs區域的内容。 */
       regs =task_pt_regs(child);
       ret = 0;  /* Default return value. */
 
       switch (addr) {
       case 0 ... 31:    /* 通用寄存器 */
           tmp =regs->regs[addr];
           break;
       case FPR_BASE ...FPR_BASE + 31:
           ......
           break;
       case PC:
           tmp =regs->cp0_epc;
           break;
       case CAUSE:
           tmp =regs->cp0_cause;
           break;
       case BADVADDR:
           tmp =regs->cp0_badvaddr;
           break;
       case MMHI:
           tmp = regs->hi;
           break;
       case MMLO:
           tmp = regs->lo;
           break;
       case FPC_CSR:
           tmp =child->thread.fpu.fcr31;
           break;
       case FPC_EIR: {   /* implementation / version register */
           ......
           break;
       }
       case DSP_BASE ...DSP_BASE + 5: {
           ......
           break;
       }
       case DSP_CONTROL:
           ......
           break;
       default:
           tmp = 0;
           ret = -EIO;
           goto out;
       }
       ret = put_user(tmp,(unsigned long __user *) data);
       break;
    }
           

PTRACE_POKETEXT,PTRACE_POKEDATA

将data的内容拷貝到程序位址空間中addr指向的位址。

PTRACE_POKEUSR

将data的内容拷貝到程序USER區域中偏移為addr的地方,一般addr要求是word-aligned的。由于要修改USER區域,核心為保證完整健全,會禁止某些域被修改。

strace工具的實作原理

strace工具是一個使用者态的應用程式,用來追蹤程序的系統調用。它的基礎就是ptrace系統調用。安裝strace之後,就可以使用strace指令了。

最簡單的strace指令的用法就是:strace PROG,PROG是要執行的程式。strace指令執行的結果就是按照調用順序列印出所有的系統調用,包括函數名、參數清單以及傳回值。

使用strace跟蹤一個程序的系統調用的基本流程如圖1所示。

strace工具的實作原理ptrace系統調用strace工具的實作原理

圖1 strace實作流程

從圖中可以看出strace做了以下幾件事情:

1.   設定SIGCHLD 信号的處理函數,這個處理函數隻要不是SIG_IGN即可。由于子程序停止後是通過SIGCHLD信号通知父程序的,是以這裡要防止SIGCHLD信号被忽略。

2.   建立子程序,在子程序中調用ptrace(PTRACE_TRACEME,0L, 0L, 0L)使其被父程序跟蹤,并通過execv函數執行被跟蹤的程式。

3.   通過wait()等待子程序停止,并獲得子程序停止時的狀态status。

4.   通過子程序的狀态檢視子程序是否已正常退出,如果是,則不再跟蹤,随後調用ptrace發送PTRACE_DETACH請求解除跟蹤關系。

5.   子程序停止後,列印系統調用的函數名、參數和傳回值。具體流程見圖2。

6.   通過PTRACE_SYSCALL讓子程序繼續運作,由于這個請求會讓子程序在系統調用的入口處和系統調用完成時都會停止并通知父程序,這樣,父程序就可以在系統調用開始之前獲得參數,結束之後獲得傳回值。

在系統調用的入口和結束時子程序停止運作時,這時父程序認為子程序是因為收到SIGTRAP信号而停止的。是以父程序在wait()後可以通過SIGTRAP來與其他信号區分開。

Strace中為每個要跟蹤的程序維護了一個TCB(Trace Control Block)結構,定義如下。它儲存了目前發生的系統調用的資訊。

/* Trace Control Block */
struct tcb {
    int flags;    /* See below for TCB_ values */
    int pid;      /* Process Id of this entry */
    int qual_flg; /* qual_flags[scno] or DEFAULT_QUAL_FLAGS + RAW*/
    int u_error;  /* Error code */
    long scno;    /* System call number */
    long u_arg[MAX_ARGS];    /* System call arguments */
    long u_rval;      /* Return value */
    int curcol;       /* Output column for this process */
    FILE *outf;       /* Output file for this process */
    const char *auxstr;/*Auxiliary info from syscall (see RVAL_STR) */
    const struct_sysent *s_ent;/* sysent[scno] or dummy struct for bad scno */
    struct timeval stime;/*System time usage as of last process wait */
    struct timeval dtime;    /* Delta for system time usage */
    struct timeval etime;    /* Syscall entry time */
              /* Support fortracing forked processes: */
    long inst[2];     /* Saved clone args (badly named) */
};
           

上面已經提到,子程序會在系統調用前後各停止一次,是以列印系統調用資訊時分為兩個階段:在系統調用開始時可以擷取系統調用号和參數,在系統調用結束時可以擷取系統調用的傳回結果。通過給tcb結構的flags字段清除和添加TCB_INSYSCALL标志位來區分系統調用的開始和結束。

strace工具的實作原理ptrace系統調用strace工具的實作原理

圖2 strace中列印系統調用的實作流程

例如編寫一個使用printf列印“Hello world”的程式hello.c,使用strace跟蹤該程式的系統調用可以看到如下結果:

# ./strace ./hello
execve("./hello ", ["./hello "], [/* 7 vars */])= 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaad000
stat("/etc/ld.so.cache", 0x7faf4ca8)    = -1 ENOENT (No such file or directory)
open("/tmp/libgcc_s.so.1", O_RDONLY)    = -1 ENOENT (No such file or directory)
open("/lib/libgcc_s.so.1", O_RDONLY)    = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=1565445, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaae000
read(3,"\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0\263\200\0\0\0004"...,4096) = 4096
mmap(NULL, 241664, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =0x2aabe000
mmap(0x2aabe000, 169308, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED,3, 0) = 0x2aabe000
mmap(0x2aaf8000, 2400, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,3, 0x2a000) = 0x2aaf8000
close(3)                                = 0
munmap(0x2aaae000, 4096)                = 0
open("/tmp/libc.so.0", O_RDONLY)        = -1 ENOENT (No such file or directory)
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=431732, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaae000
read(3, "\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0\252\200\0\0\0004"...,4096) = 4096
mmap(NULL, 471040, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =0x2aaf9000
mmap(0x2aaf9000, 380336, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED,3, 0) = 0x2aaf9000
mmap(0x2ab65000, 8088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,3, 0x5c000) = 0x2ab65000
mmap(0x2ab67000, 19376, PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x2ab67000
close(3)                                = 0
munmap(0x2aaae000, 4096)                = 0
open("/tmp/libc.so.0", O_RDONLY)        = -1 ENOENT (No such file or directory)
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=431732, ...}) = 0
close(3)                                = 0
stat("/lib/ld-uClibc.so.0", {st_mode=S_IFREG|0755,st_size=22604, ...}) = 0
mprotect(0x2ab65000, 4096, PROT_READ)   = 0
mprotect(0x2aabc000, 4096, PROT_READ)   = 0
ioctl(0, TIOCNXCL, {B115200 opost isig icanon echo ...}) = 0
ioctl(1, TIOCNXCL, {B115200 opost isig icanon echo ...}) = 0
write(1, "Hello world\n", 12Hello world
)           = 12
exit(0)                                 = ?
+++ exited with 0 +++
#
           

從結果可以看出,執行該程式調用了很多系統調用,并最終通過write系統調用列印出“Hello world”。

跟蹤一個正在運作的程序,使用-p選項加上程序的pid。

跟蹤某個特定的系統調用,使用-e選項加上系統調用名。

例如,跟蹤程序727的epoll_wait系統調用:strace -e epoll_wait -p 727

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

从去年12月份「高洁丝卫生巾」被曝有虫卵,还不满3个月,另外一个大品牌七度空间也因为“虫卵”翻车。

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

在这条微博的下面有这样一条热评,虽说很调侃,但却实实在在体现了女性朋友们在选择卫生巾产品上的无奈与被动。

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

而这不是七度空间第一次翻车,更不是卫生巾品牌第一次翻车了。

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

护舒宝、高洁丝、七度空间、洁婷...

不论哪一个品牌爆了雷,对于消费者来说,都是一种心理负担。

目前,七度空间品牌方对此事也作了回应:将有虫卵的卫生巾送交第三方检测。

我们也蹲个后续,看看这个虫卵到底是怎么来的。

虫卵

可能是怎么来的?

具体的结果,我们也只能在第三方检测结果出来知晓。

但卫生巾出现虫卵,根据菌菌查阅的相关资料来看,无非就是这三个环节:

生产制作环节、运输环节、储存环节。

我们先来说说生产制作环节上的问题。

其实如果按照国家标准来执行,卫生巾确实不太可能在生产制作过程中沾染虫和虫卵。

大陆卫生巾(护垫)现行国标是2019年7月1日起实行的GB/T8939-2018。

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

这一标准详细规定了卫生巾的技术要求、试验方法、检验规则及标识、包装、运输等要求。

符合标准的卫生用品,是无异物无污染、微生物合规、生产场所菌落合规并有防虫措施的:

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病
七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

卫生标准十分严格,微小的微生物都不会放过。

所以,如果严格遵照国家标准进行生产操作,那出现虫卵的可能性非常非常低。

但如果确实是生产线上的问题,那就证明整个生产线出现了系统性的卫生崩溃。

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

所以,如果这次的调查结果确实是生产线上的问题,那同批次所有的产品都可能暴露了这样的问题,那基本上这些产品都不能使用了。

第二个就是运输与仓储方面可能存在的问题。

如果存储运输中密封不严,或者仓储环境潮湿,也可能会出现虫卵这样的问题。

一般情况下,产品包装好后下了生产线,要经过运输、二段仓储、经销商仓储等多个环境。

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

如果运输仓储过程中出现包装破损导致密封不严,即使是很小的裂痕,也会给小虫子们提供可乘之机。

除了商家的储存,消费者自我储存不当也可能会出现类似的问题。

在国标中,有明确的存储要求:

成品应保存在干燥通风,不受阳光直接照射的室内,防止雨雪淋袭和地面湿气的影响,不得与有污染或有毒化学品共存。

如果拆分后长期放置在卫生间等较为潮湿的环境中,确实也会出现虫卵。

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

高洁丝虫卵事件,当时菌菌也关注了后续:

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

这也算是给大众了一个定心丸,接下来我们就静待七度空间的检测结果。

不合格卫生巾

对私处的伤害

根据大陆卫生巾(护垫)现行国标GB/T8939-2018,产品是否合格涉及很多方面,主要包括:

PH、甲醛含量、可迁移性荧光物质、背胶剥离强度、细菌真菌含量等等。

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病
七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

每个方面都有十分严格的要求,其在满足舒适度之外,更多的是对安全方面的保证。

菌菌查阅了很多资料与研究,很多研究结果都指向不合格的卫生巾会对女性私处造成伤害。

其中一项研究发现:不卫生的月经习惯与生殖道感染(细菌性阴道炎、念珠菌感染与滴虫性阴道炎)患病率之间强相关,其中不卫生的月经管理就包括使用不合格的卫生产品。

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

并且根据统计资料表明,在妇科病门诊就诊的病人中,有3%~5%的病人是因卫生巾使用不当引起的,如霉菌性阴道炎、过敏性皮炎、尿路感染等疾病。

不合格的产品,细菌菌落总数、PH值、甲醛含量、大肠菌落、致病性化脓菌等等都存在超标。

其中像甲醛主要来源于背胶原料,很多卫生巾厂家为了降低产品生产成本,会专门使用劣质粘胶剂,这样劣质的粘胶剂中的甲醛、苯含量会严重超标。

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

关于甲醛与苯的危害,菌菌就不多说了,大家都知道甲醛是世界卫生组织确定的致癌和致畸性物质。

而这部分不合格的产品主要集中在小厂生产与三无产品中,相对来说,大品牌、正规商家这方面的问题是比较少的。

而这两次曝光的虫卵问题,它对大众的伤害其实...更集中在心理上。

说实话,即使心理素质过硬,看见一排排的虫卵,内心都会很膈应。

而这两次的爆雷事件对女性朋友们更大的影响是:可信任产品减少、无可靠产品使用。

我们还能用什么?

大牌产品一个接一个暴雷,我们还能用什么?

这也是我们作为消费者最无奈的事情,我们可以选择任何产品,但产品却不为我们的选择做任何保障。

更滑稽的场面可能就是文章开头里的那个热评:

我们因为一个产品爆雷,去选择了一个看起来更安全的,没想到它也爆雷。

选择一个好产品,难啊。

而这一点,说实话我们只能依赖相关部门的监管、监督,希望七度空间的后续会再次顶上热搜:

不仅仅是给大众一个交代,更是为女性朋友们保留一个可靠选择!

除了相信相关部门,我们作为使用者,也要在选与看上多留心。

悲观点说,如果发现问题,还能维权不是?

七度空间卫生巾被查出虫卵!这些卫生巾慎用,会得妇科病

关于品牌方面的问题,菌菌确实没办法帮大家推荐,但是能给大家一个选择方法:

在购买前,可以在官方旗舰店下看差评,如果确实差评如潮,那就弃了。

我们大概算一算,假设一个女生13岁月经初潮、50岁绝经、月经周期为28天,那她一生会来494次月经。

每次月经出血量60 mL,把一片容量为10 mL 的普通日用卫生巾完全浸透计算,一名女生一生要用2964 片卫生巾!

接近3000片的数量,如果产品不合格,那对身体就是一场灾难性的伤害。

我们也希望在两个知名品牌之后,关于卫生巾安全、卫生问题,能在各方的推动下不断解决与提高。

这可不是有卫生巾可用就行了。