Implementing a Golang Debugger (Part III)

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed. So far we have known how to step through a process that is paused with Ptrace (Tracee) and how to get some debug information from the binaries ([here] (https://studygolang.com/articles/12794) to read the relevant content). The next step is to set breakpoints, wait for the program to run to the breakpoint, and see the process-related information. Let's start with the assembly code used in the previous article "Asmsection. Datamsg db" Hello, world! ", 0xAlen equ $-msgsection. Textglobal _start_start:mov Rax, 1; Write Syscall (https://linux.die.net/man/2/write) mov rdi, 1; Stdoutmov RSI, Msgmov RDX, Len; Passing parameters to ' syscall ' instruction described in; Https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscallsyscallmov Rax, 60; Exit Syscall (https://linux.die.net/man/2/exit) mov rdi, 0; Exit Codesyscall "We set the breakpoint at the following line of code," "Asmmov rdi, 1" So when the code is executed, the program pauses, checking to see if the value stored in the RDI register is 0, and then stepping through the line of code, Check to see if this value becomes 1. # # breakpoints have an [interrupt instruction] (Https://en.wikipedia.org/wiki/INT_%28x86_instruction%29) in the x86 system, which can generate a soft interrupt. In a Linux system, this soft interrupt is implemented by F.ex calling Syscall. A dedicated system directive was introduced in the X86-64 system to achieve this soft interrupt, which is faster than the instructions in the x86 system, which is why I use this special instruction. But we can use a normal INT interrupt to do the same task. We use INT three to set a breakpoint, it corresponds to the opcode is 0xCCINT 3 instruction will generate a size of one byteSpecial opcode (CC), which can invoke the exception handler (this opcode is very useful because it can be used to replace the first byte of any instruction, make it a breakpoint, and then add an extra byte without affecting the other code), see the following document for details [Intel 64 and IA-32 system software User manual] (HTTPS://SOFTWARE.INTEL.COM/EN-US/ARTICLES/INTEL-SDM) We replace the first byte of a particular instruction with 0xCC, making it a breakpoint, and once the breakpoint is set, We can do the following things 1. View process Status 2. Replace the 0xCC opcode with the original value 3. Reduce the counter value of the program by 14. The first question we need to address in order to execute an instruction is: Where to place 0xCC, we do not know the move RDI, 1 This instruction in the memory of the specific location. Since this is the second instruction, it is the memory address of this instruction to add the first instruction to move Rax, the length of the 1, at the beginning of the program's memory address. Since the instruction length in the x86 system is not fixed, it becomes more difficult to make sure that the command starts the address. The position of the first instruction of the program can be obtained by letting the program stop when no instruction is executed (as we have done before), the length of the first instruction can be obtained by objdump command: "' shell> nasm-f elf64-o hello.o src/ Github.com/mlowicki/hello/hello.asm && ld-o/go/bin/hello hello.o> objdump-d-M intel/go/bin/hello/go/bin/ Hello:file format elf64-x86-64disassembly of the section. Text:00000000004000b0 <_start>:4000b0:b8-xx-XX mov eax , 0X14000B5:BF to xx edi,0x14000ba:48 be d8 xx rsi,0x6000d84000c1:00 xx movabs 004000c4:ba 0e 00 00 XX mov edx,0xe4000c9:0f syscall4000cb:b8 3c xx xx mov eax,0x3c4000D0:BF xx xx edi,0x04000d5:0f syscall "" From the above output we can find the length of the first instruction is 5 bytes (4000b5-4000b0), so we have to put 0xCC in the position of the first instruction Add 5 byte Place, below is the code implementation ' Gopackage Mainimport ("Flag" "Log" "OS" "Os/exec" "Syscall") func step (pid int) {err: = Syscall. Ptracesinglestep (PID) if err! = Nil {log. Fatal (ERR)}}func cont (PID int) {err: = Syscall. Ptracecont (PID, 0) If err! = Nil {log. Fatal (Err)}}func SETPC (PID int, PC UInt64) {var regs syscall. Ptraceregserr: = Syscall. Ptracegetregs (PID, &regs) if err! = Nil {log. Fatal (Err)}regs. SETPC (PC) Err = Syscall. Ptracesetregs (PID, &regs) if err! = Nil {log. Fatal (Err)}}func GETPC (pid int) UInt64 {var regs syscall. Ptraceregserr: = Syscall. Ptracegetregs (PID, &regs) if err! = Nil {log. Fatal (Err)}return regs. PC ()}func setbreakpoint (PID int, breakpoint uintptr) []byte {Original: = make ([]byte, 1) _, Err: = Syscall. Ptracepeekdata (PID, breakpoint, original) if err! = Nil {log. Fatal (Err)}_, err = Syscall. Ptracepokedata (PID, breakpoint, []BYTE{0XCC}) If err! = Nil {log. Fatal (ERR)}return original}func clearbreakpoint (PID int, breakpoint UIntPtr, original []byte) {_, Err: = Syscall. Ptracepokedata (PID, breakpoint, original) if err! = Nil {log. Fatal (Err)}}func printstate (pid int) {var regs syscall. Ptraceregserr: = Syscall. Ptracegetregs (PID, &regs) if err! = Nil {log. Fatal (Err)}log. Printf ("rax=%d, rdi=%d\n", regs. Rax, Regs. Rdi)}func Main () {flag. Parse () Input: = flag. ARG (0) cmd: = Exec.command (input) cmd. Args = []string{input}cmd. Stdout = OS. Stdoutcmd.stderr = OS. Stderrcmd.sysprocattr = &syscall. Sysprocattr{ptrace:true}err: = cmd. Start () if err! = Nil {log. Fatal (err)}err = cmd. Wait () log. Printf ("state:%v\n", err) pid: = cmd. Process.pidbreakpoint: = UIntPtr (GETPC (PID) + 5) Original: = Setbreakpoint (PID, Breakpoint) cont (PID) var ws syscall. Waitstatus_, err = Syscall. WAIT4 (PID, &ws, Syscall. WALL, nil) clearbreakpoint (PID, breakpoint, original) printstate (PID) SETPC (PID, UInt64 (breakpoint)) Step (PID) _, Err = Syscall. WAIT4 (PID, &ws, Syscall. WALL, nil) printstate (PID)The source file starts with some helper functions, SETPC and GETPC are used to maintain [program counter] (Https://en.wikipedia.org/wiki/Program_counter), and the register PC holds the next instruction to be executed. If the program is paused without executing any instructions, the value in the PC is the memory address of the first instruction of the program. The functions that maintain breakpoints (Setbreakpoint and Clearbreakpoint) are responsible for inserting or removing the opcode 0xCC in the instruction, and the following is the output of the program: ' shell> go install github.com/mlowicki/ breakpoint> breakpoint/go/bin/hello2017/07/16 21:06:33 state:stop signal:trace/breakpoint trap2017/07/16 21:06:33 Rax=1, rdi=02017/07/16 21:06:33 rax=1, rdi=1 ' output as we expected, when the program arrives at the breakpoint, the RDI register is not set (the value is 0), after executing the next instruction (the second instruction), Its value becomes the following instruction set the value "' Asmmov rdi, 1 ' Now we have finished the task listed in the beginning of the article. Of course, our program also needs some functions to calculate the length of the instruction, but do not worry, we will implement these functions after # # REPL now is the time to implement the basic framework of the debugger, this is a simple command-line program, the program loop waiting for user input like "set a breakpoint at" and "go Single Step "command" Gopackage mainimport ("Bufio" "Flag" "FMT" "io" "Log" "OS" "Os/exec" "Strings" "Syscall") func Inittracee (path string) int {cmd: = exec. Command (path) cmd. Args = []string{path}cmd. Stdout = OS. Stdoutcmd.stderr = OS. Stderrcmd.sysprocattr = &syscall. Sysprocattr{ptrace:true}err: = cmd. Start () if err! = Nil {log. Fatal (ERR)}err = cmd. Wait ()//Process should be stopped here because of trace/breakpoint trapif err = = Nil {log. Fatal ("program exited")}return cmd. Process.pid}func Main () {flag. Parse () _ = INITTRACEE (flag. ARG (0)) for {reader: = Bufio. Newreader (OS. Stdin) fmt. Print (">") command, err: = Reader. ReadString (' \ n ') if err! = Nil {if Err = = Io. EOF {fmt. Println () Break}log. Fatal (err)}command = Command[:len (command)-1]//get rid of ending newline characterif strings. Hasprefix (Command, "register") {FMT. PRINTLN ("Register ...")} else if strings. Hasprefix (Command, "breakpoint") {FMT. Println ("Breakpoint ...")} else if command = = "Help" {FMT. Println ("Help ...")} else if command = = "Step" {FMT. PRINTLN ("Step ...")} else if command = = "Continue" {FMT. Println ("Continue")} else {fmt. PRINTLN ("Unknown Command")}} "This is the basic framework of our debugger, it does not provide much functionality (currently), but it already has a [REPL] (https://en.wikipedia.org/wiki/ Read%e2%80%93eval%e2%80%93print_loop) The necessary logic of the environment. We know more or less about how to implement common debug commands like Next, continue, view variable states, and we'll implement these features shortly. One of the less clear points in Golang is the breakpoint command. This will be detailed in the following articleExplain why it is more difficult in golang than imagined and how to overcome these complexities.

via:https://medium.com/golangspec/making-debugger-in-golang-part-iii-5aac8e49f291

Author: Michałłowicki Translator: Jettyhan proofreading: polaris1119

This article by GCTT original compilation, go language Chinese network honor launches

This article was originally translated by GCTT and the Go Language Chinese network. Also want to join the ranks of translators, for open source to do some of their own contribution? Welcome to join Gctt!
Translation work and translations are published only for the purpose of learning and communication, translation work in accordance with the provisions of the CC-BY-NC-SA agreement, if our work has violated your interests, please contact us promptly.
Welcome to the CC-BY-NC-SA agreement, please mark and keep the original/translation link and author/translator information in the text.
The article only represents the author's knowledge and views, if there are different points of view, please line up downstairs to spit groove

380 Reads
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.