`
njs458sd
  • 浏览: 13984 次
社区版块
存档分类
最新评论

ptrace源代码分析

 
阅读更多

ptrace源代码分析
2011年01月04日
  ptrace作为应用程序调试的基石,要想对其有深入的了解,最好的方法是分析它的源代码。选取linux2.6.8,更高版本的内容基本相同。实现ptrace系统调用功能的主要是sys_ptrace函数,当然还包括一些读写寄存器的辅助函数。该函数的基本结构比较简单:
  (1)判断该进程是否被跟踪,即request==PTRACE_TRACEME,如果是,对其进行处理。
  (2)根据被跟踪子进程的pid找到其task结构体 
  (3)判断是否为init进程(pid==1)或者是自身进程current,init进程是计算机上电启动后执行的第一个进程,也是所有进程的父进程,它不能被跟踪。
  (4)如果request==PTRACE_ATTACH,则将父进程附着在子进程上,并检查是否扶着成功。该命令实现的功能是父进程监视一个已经在运行的子进程。
  (5)上述步骤完成后,就可以根据request的命令对子进程进行各种操作。
  该函数有个关键词asmlinkage是指明该函数用堆栈来传递参数。是汇编程序向相应的C语言程序传递参数的一种方式。其源代码如下:(linux/arch/i386/kernel/ptrace.c) 233 asmlinkage int sys_ptrace(long request, long pid, long addr, long data) 234 { 235 struct task_struct *child; 236 struct user * dummy = NULL; 237 int i, ret; 238 unsignedlong __user *datap = (unsignedlong __user *)data; 239 240 lock_kernel(); 241 ret = -EPERM; 242 if (request == PTRACE_TRACEME) { 243 /* are we already being traced? */ 244 if (current->ptrace & PT_PTRACED) 245 goto out; 246 ret = security_ptrace(current->parent, current); 247 if (ret) 248 goto out; 249 /* set the ptrace bit in the process flags. */ 250 current->ptrace |= PT_PTRACED; 251 ret = 0; 252 goto out; 253 } 254 ret = -ESRCH; 255 read_lock(&tasklist_lock); 256 child = find_task_by_pid(pid); 257 if (child) 258 get_task_struct(child); 259 read_unlock(&tasklist_lock); 260 if (!child) 261 goto out; 262 263 ret = -EPERM; 264 if (pid == 1) /* you may not mess with init */ 265 goto out_tsk; 266 267 if (request == PTRACE_ATTACH) { 268 ret = ptrace_attach(child); 269 goto out_tsk; 270 } 271 272 ret = ptrace_check_attach(child, request == PTRACE_KILL); 273 if (ret 0) 274 goto out_tsk; 275 276 switch (request) { 277 /* when I and D space are separate, these will need to be fixed. */ 278 case PTRACE_PEEKTEXT: /* read word at location addr. */ 279 case PTRACE_PEEKDATA: { 280 unsignedlong tmp; 281 int copied; 282 283 copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); 284 ret = -EIO; 285 if (copied != sizeof(tmp)) 286 break; 287 ret = put_user(tmp, datap); 288 break; 289 } 290 291 /* read the word at location addr in the USER area. */ 292 case PTRACE_PEEKUSR: { 293 unsignedlong tmp; 294 295 ret = -EIO; 296 if ((addr & 3) || addr 0 || 297 addr >sizeof(struct user) - 3) 298 break; 299 300 tmp = 0; /* Default return condition */ 301 if(addr FRAME_SIZE*sizeof(long)) 302 tmp = getreg(child, addr); 303 if(addr >= (long) &dummy->u_debugreg[0] && 304 addr (long) &dummy->u_debugreg[7]){ 305 addr -= (long) &dummy->u_debugreg[0]; 306 addr = addr >> 2; 307 tmp = child->thread.debugreg[addr]; 308 } 309 ret = put_user(tmp, datap); 310 break; 311 } 312 313 /* when I and D space are separate, this will have to be fixed. */ 314 case PTRACE_POKETEXT: /* write the word at location addr. */ 315 case PTRACE_POKEDATA: 316 ret = 0; 317 if (access_process_vm(child, addr, &data, sizeof(data), 1) == sizeof(data)) 318 break; 319 ret = -EIO; 320 break; 321 322 case PTRACE_POKEUSR: /* write the word at location addr in the USER area */ 323 ret = -EIO; 324 if ((addr & 3) || addr 0 || 325 addr >sizeof(struct user) - 3) 326 break; 327 328 if (addr FRAME_SIZE*sizeof(long)) { 329 ret = putreg(child, addr, data); 330 break; 331 } 332 /* We need to be very careful here. We implicitly 333 want to modify a portion of the task_struct, and we 334 have to be selective about what portions we allow someone 335 to modify. */ 336 337 ret = -EIO; 338 if(addr >= (long) &dummy->u_debugreg[0] && 339 addr (long) &dummy->u_debugreg[7]){ 340 341 if(addr == (long) &dummy->u_debugreg[4]) break; 342 if(addr == (long) &dummy->u_debugreg[5]) break; 343 if(addr (long) &dummy->u_debugreg[4] && 344 ((unsignedlong) data) >= TASK_SIZE-3) break; 345 346 if(addr == (long) &dummy->u_debugreg[7]) { 347 data &= ~DR_CONTROL_RESERVED; 348 for(i=0; i; i++) 349 if ((0x5f54 >> ((data >> (16 + 4*i)) & 0xf)) & 1) 350 goto out_tsk; 351 } 352 353 addr -= (long) &dummy->u_debugreg; 354 addr = addr >> 2; 355 child->thread.debugreg[addr] = data; 356 ret = 0; 357 } 358 break; 359 360 case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */ 361 case PTRACE_CONT: { /* restart after signal. */ 362 long tmp; 363 364 ret = -EIO; 365 if ((unsignedlong) data > _NSIG) 366 break; 367 if (request == PTRACE_SYSCALL) { 368 set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); 369 } 370 else { 371 clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); 372 } 373 child->exit_code = data; 374 /* make sure the single step bit is not set. */ 375 tmp = get_stack_long(child, EFL_OFFSET) & ~TRAP_FLAG; 376 put_stack_long(child, EFL_OFFSET,tmp); 377 wake_up_process(child); 378 ret = 0; 379 break; 380 } 381 382 /* 383 * make the child exit. Best I can do is send it a sigkill. 384 * perhaps it should be put in the status that it wants to 385 * exit. 386 */ 387 case PTRACE_KILL: { 388 long tmp; 389 390 ret = 0; 391 if (child->state == TASK_ZOMBIE) /* already dead */ 392 break; 393 child->exit_code = SIGKILL; 394 /* make sure the single step bit is not set. */ 395 tmp = get_stack_long(child, EFL_OFFSET) & ~TRAP_FLAG; 396 put_stack_long(child, EFL_OFFSET, tmp); 397 wake_up_process(child); 398 break; 399 } 400 401 case PTRACE_SINGLESTEP: { /* set the trap flag. */ 402 long tmp; 403 404 ret = -EIO; 405 if ((unsignedlong) data > _NSIG) 406 break; 407 clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); 408 if ((child->ptrace & PT_DTRACE) == 0) { 409 /* Spurious delayed TF traps may occur */ 410 child->ptrace |= PT_DTRACE; 411 } 412 tmp = get_stack_long(child, EFL_OFFSET) | TRAP_FLAG; 413 put_stack_long(child, EFL_OFFSET, tmp); 414 child->exit_code = data; 415 /* give it a chance to run. */ 416 wake_up_process(child); 417 ret = 0; 418 break; 419 } 420 421 case PTRACE_DETACH: 422 /* detach a process that was attached. */ 423 ret = ptrace_detach(child, data); 424 break; 425 426 case PTRACE_GETREGS: { /* Get all gp regs from the child. */ 427 if (!access_ok(VERIFY_WRITE, datap, FRAME_SIZE*sizeof(long))) { 428 ret = -EIO; 429 break; 430 } 431 for ( i = 0; i FRAME_SIZE*sizeof(long); i += sizeof(long) ) { 432 __put_user(getreg(child, i), datap); 433 datap++; 434 } 435 ret = 0; 436 break; 437 } 438 439 case PTRACE_SETREGS: { /* Set all gp regs in the child. */ 440 unsignedlong tmp; 441 if (!access_ok(VERIFY_READ, datap, FRAME_SIZE*sizeof(long))) { 442 ret = -EIO; 443 break; 444 } 445 for ( i = 0; i FRAME_SIZE*sizeof(long); i += sizeof(long) ) { 446 __get_user(tmp, datap); 447 putreg(child, i, tmp); 448 datap++; 449 } 450 ret = 0; 451 break; 452 } 453 454 case PTRACE_GETFPREGS: { /* Get the child FPU state. */ 455 if (!access_ok(VERIFY_WRITE, datap, 456 sizeof(struct user_i387_struct))) { 457 ret = -EIO; 458 break; 459 } 460 ret = 0; 461 if (!child->used_math) 462 init_fpu(child); 463 get_fpregs((struct user_i387_struct __user *)data, child); 464 break; 465 } 466 467 case PTRACE_SETFPREGS: { /* Set the child FPU state. */ 468 if (!access_ok(VERIFY_READ, datap, 469 sizeof(struct user_i387_struct))) { 470 ret = -EIO; 471 break; 472 } 473 child->used_math = 1; 474 set_fpregs(child, (struct user_i387_struct __user *)data); 475 ret = 0; 476 break; 477 } 478 479 case PTRACE_GETFPXREGS: { /* Get the child extended FPU state. */ 480 if (!access_ok(VERIFY_WRITE, datap, 481 sizeof(struct user_fxsr_struct))) { 482 ret = -EIO; 483 break; 484 } 485 if (!child->used_math) 486 init_fpu(child); 487 ret = get_fpxregs((struct user_fxsr_struct __user *)data, child); 488 break; 489 } 490 491 case PTRACE_SETFPXREGS: { /* Set the child extended FPU state. */ 492 if (!access_ok(VERIFY_READ, datap, 493 sizeof(struct user_fxsr_struct))) { 494 ret = -EIO; 495 break; 496 } 497 child->used_math = 1; 498 ret = set_fpxregs(child, (struct user_fxsr_struct __user *)data); 499 break; 500 } 501 502 case PTRACE_GET_THREAD_AREA: 503 ret = ptrace_get_thread_area(child, addr, 504 (struct user_desc __user *) data); 505 break; 506 507 case PTRACE_SET_THREAD_AREA: 508 ret = ptrace_set_thread_area(child, addr, 509 (struct user_desc __user *) data); 510 break; 511 512 default: 513 ret = ptrace_request(child, request, addr, data); 514 break; 515 } 516 out_tsk: 517 put_task_struct(child); 518 out: 519 unlock_kernel(); 520 return ret; 521 }
  主要分析一下PEEKUSER命令实现的部分:其他的requset命令实现类似。 292 case PTRACE_PEEKUSR: { 293 unsignedlong tmp; 294 295 ret = -EIO; 296 if ((addr & 3) || addr 0 || 297 addr >sizeof(struct user) - 3) 298 break; 299 300 tmp = 0; /* Default return condition */ 301 if(addr FRAME_SIZE*sizeof(long)) 302 tmp = getreg(child, addr); 303 if(addr >= (long) &dummy->u_debugreg[0] && 304 addr (long) &dummy->u_debugreg[7]){ 305 addr -= (long) &dummy->u_debugreg[0]; 306 addr = addr >> 2; 307 tmp = child->thread.debugreg[addr]; 308 } 309 ret = put_user(tmp, datap); 310 break; 311 }
  PEEKUSER实现的功能是读取用户user的寄存器值包括调试寄存器的值。第296行判断地址是否对齐,越界,合法。第301行宏定义FRAME_SIZE=17,是通用寄存器的个数。它们分别是EBX、ECX、EDX、ESI、EDI、EBP、EAX、DS, ES、FS、GS、ORIG_EAX、EIP、CS、EFLAGS、ESP、SS。用getreg来读取这些寄存器的值.getreg函数原型如下: 114 staticunsignedlong getreg(struct task_struct *child, 115 unsignedlong regno) 116 { 117 unsignedlong retval = ~0UL; 118 119 switch (regno >> 2) { 120 case FS: 121 retval = child->thread.fs; 122 break; 123 case GS: 124 retval = child->thread.gs; 125 break; 126 case DS: 127 case ES: 128 case SS: 129 case CS: 130 retval = 0xffff; 131 /* fall through */ 132 default: 133 if (regno > GS*4) 134 regno -= 2*4; 135 regno = regno - sizeof(struct pt_regs); 136 retval &= get_stack_long(child, regno); 137 } 138 return retval; 139 } 140
  4 #define EBX 0 5 #define ECX 1 6 #define EDX 2 7 #define ESI 3 8 #define EDI 4 9 #define EBP 5 10 #define EAX 6 11 #define DS 7 12 #define ES 8 13 #define FS 9 14 #define GS 10 15 #define ORIG_EAX 11 16 #define EIP 12 17 #define CS 13 18 #define EFL 14 19 #define UESP 15 20 #define SS 16 21 #define FRAME_SIZE 17
  进程结构体TSS中存有所有寄存器的值,但在子进程被调试时处于核心态,不能够直接读取寄存器的值,所以getreg只能读取用户堆栈中的寄存器的值。不过fs,gs寄存器的值需要从TSS中读取。ds,ss,cs es均为16位,故高16位值不管。
  第136行即利用get_stack_long函数从堆栈中读出其他寄存器的值。该函数定义如下:
  51 static inline int get_stack_long(struct task_struct *task, int offset) 52 { 53 unsignedchar *stack; 54 55 stack = (unsignedchar *)task->thread.esp0; 56 stack += offset; 57 return (*((int *)stack)); 58 }
  esp0是堆栈指针,通用的寄存器在堆栈中按顺序排放,通过偏移量0ffset便可以依次读取。第303行到308行是读取调试寄存器的值。
  因此,总的来说,ptrace系统调用最主要的是核心函数是sys_ptarce函数,并在该函数中调用了寄存器的辅助读写函数,内存辅助读写函数,通过传入各种request命令,实现了强大的调试功能。 
分享到:
评论

相关推荐

    Linux内核源代码情景分析 (上下册 高清非扫描 )

    丛书名: Linux内核源代码情景分析 出版社:浙江大学出版社 目录 第1章 预备知识 1.1 Linux内核简介. 1.2 Intel X86 CPU系列的寻址方式 1.3 i386的页式内存管理机制 1.4 Linux内核源代码中的C语言代码 1.5 Linux...

    Linux源码分析(ptrace)

    Ptrace 提供了一种父进程可以控制子进程运行,并可以检查和改变它的核心image。它主要用于实现断点调试。一个被跟踪的进程运行中,直到发生一个信号。则进程被中止,并且通知其父进程。在进程中止的状态下,进程的...

    莱昂氏UNIX源代码分析(全面剖析unix)PDF

    下篇 莱昂氏UNIX源代码分析 前言 207 第1章 绪论 209 1.1 UNIX操作系统 209 1.2 公用程序 209 1.3 其他文档 210 1.4 UNIX程序员手册 210 1.5 UNIX文档 211 1.6 UNIX操作系统源代码 211 1.7 源代码中各部分 212 1.8 ...

    linux内核源代码情景分析

    《linux内核源代码情景分析》(非扫描电子版本) 第1章 预备知识 1.1 Linux内核简介 1.2 Intel X86 CPU系列的寻址方式 1.3 i386的页式内存管理机制 1.4 Linux内核源代码中的C语言代码 1.5 Linux内核源代码中的...

    linux 内核源代码分析

    1. 4 Linux内核源代码中的C语言代码 1.5 Linux内核源代码中的汇编语言代码 第2章 存储管理 2.1 Linux内存管理的基本框架 2.2 地址映射的全过程 2.3 几个重要的数据结构和函数 2.4 越界访问 2.5 用户...

    zygote源码分析

    zygote源码分析

    莱昂氏UNIX源代码分析

    上篇为UNIX版本6的源代码,下篇是莱昂先生对UNIX操作系统版本6源代码的详细分析。本书语言简洁、透彻,曾作为未公开出版物广泛流传了二十多年,是一部杰出经典之作。本书适合UNIX操作系统编程人员、大专院校师生学习...

    LINUX内核源代码情景分析(上).part1.rar

    1. 4 linux内核源代码中的c语言代码 1.5 linux内核源代码中的汇编语言代码 第2章 存储管理 2.1 linux内存管理的基本框架 2.2 地址映射的全过程 2.3 几个重要的数据结构和函数 2.4 越界访问 2.5 用户...

    linux 系统源码全面剖析

    TCP源码分析 - 三次握手之 connect 过程 Linux网桥工作原理与实现 其他 定时器实现 多路复用I/O GDB原理之ptrace 容器相关 docker实现原理之 - namespace docker实现原理之 - CGroup介绍 docker实现原理之 - CGroup...

    Linux内核情景分析

    《LINUX内核源代码情景分析(下册)》图书目录如下: -------------------------------------------------------------------------------- 第 7章 基于socket的进程间通信 7.1 系统调用socket() 7.2 函数sys—...

    Linux内核情景分析(非扫描版)

    《LINUX内核源代码情景分析(下册)》图书目录如下: -------------------------------------------------------------------------------- 第7章 基于socket的进程间通信 7.1 系统调用socket() 7.2 函数sys—...

    uroboros:一个GNULinux监视和分析工具,专注于单个进程

    Uroboros是专注于单个进程的GNU / ...目前尚无二进制版本,唯一的方法是从源代码进行构建(需要go编译器,将二进制文件安装在$ GOPATH / bin中): # make sure go modules are used GO111MODULE=on go get github.co

    pyflame::fire:Pyflame:Python的跟踪分析器。 该项目已弃用,未维护

    它可以在没有显式检测的情况下获取Python调用堆栈的快照,这意味着您可以在不修改源代码的情况下对程序进行概要分析。 Pyflame能够分析嵌入式Python解释器(如 。 它完全支持对多线程Python程序进行性能分析。 与...

    LINUX编程白皮书

    第11章 Linux内核源代码 117 11.1 怎样得到Linux内核源码 117 11.2 内核源码的编排 117 11.3 从何处看起 118 第12章 Linux数据结构 120 附录A 有用的Web和FTP站点 138 附录B 词汇表 139 第二部分 Linux内核...

    matlab解开app代码-external_honggfuzz:external_honggfuzz

    matlab解开应用程序代码红毛绒 描述 面向安全,反馈驱动,易于使用的进化型模糊器,带有有趣的分析选项。 有关使用的更多数据,请参见。 它是多线程和多进程的:无需运行您的模糊器的多个副本,因为honggfuzz可以...

    Fuzzing_模糊测试--强制性安全漏洞发掘

    1.1.1 源代码评审 1.1.2 工具和自动化 1.1.3 优点和缺点 1.2 黑盒测试 1.2.1 人工测试 1.2.2 自动测试或模糊测试 1.2.3 优点和缺点 1.3 灰盒测试 1.3.1 二进制审核 1.3.2 自动化的二进制审核 1.3.3 优点和缺点 1.4 ...

    黑客反汇编揭秘(第二版).part2.rar

    3.4.3 源代码是否可用 41 3.4.4 仿真的质量 42 3.4.5 内置调试器 43 3.4.6 如何在VMware下配置SoftICE 45 第4章 汇编器入门 46 4.1 汇编语言方法论 47 4.2 基于C程序实例解释汇编概念 48 4.3 汇编插入语句 49...

    黑客反汇编揭秘(第二版).part1.rar

    3.4.3 源代码是否可用 41 3.4.4 仿真的质量 42 3.4.5 内置调试器 43 3.4.6 如何在VMware下配置SoftICE 45 第4章 汇编器入门 46 4.1 汇编语言方法论 47 4.2 基于C程序实例解释汇编概念 48 4.3 汇编插入语句 49...

    linux编程白皮书

    第11章 Linux内核源代码 117 11.1 怎样得到Linux内核源码 117 11.2 内核源码的编排 117 11.3 从何处看起 118 第12章 Linux数据结构 120 附录A 有用的Web和FTP站点 138 附录B 词汇表 139 第二部分 Linux内核模块编程...

Global site tag (gtag.js) - Google Analytics