美文网首页
chaos-mesh on mips64

chaos-mesh on mips64

作者: sarto | 来源:发表于2021-02-22 15:35 被阅读0次

    问题起因

    编译chaos-mesh v1.0.3 报错

    [root@localhost chaos-mesh]# make chaosdaemon
    GO15VENDOREXPERIMENT="1" CGO_ENABLED=1 GOOS="" GOARCH="" go build -ldflags '-s -w -X 'github.com/chaos-mesh/chaos-mesh/pkg/version.buildDate=2021-02-20T03:20:41Z' -X 'github.com/chaos-mesh/chaos-mesh/pkg/version.gitCommit=28de66e58ef60056b1f1966527fe85cc2396d670' -X 'github.com/chaos-mesh/chaos-mesh/pkg/version.gitVersion=chart-0.3.3'' -o bin/chaos-daemon ./cmd/chaos-daemon/main.go
    
    
    # github.com/chaos-mesh/chaos-mesh/pkg/ptrace
    pkg/ptrace/ptrace_linux.go:210:61: p.backupRegs.Rip undefined (type *syscall.PtraceRegs has no field or method Rip)
    pkg/ptrace/ptrace_linux.go:225:61: p.backupRegs.Rip undefined (type *syscall.PtraceRegs has no field or method Rip)
    pkg/ptrace/ptrace_linux.go:261:6: regs.Rax undefined (type syscall.PtraceRegs has no field or method Rax)
    pkg/ptrace/ptrace_linux.go:265:8: regs.Rdi undefined (type syscall.PtraceRegs has no field or method Rdi)
    pkg/ptrace/ptrace_linux.go:267:8: regs.Rsi undefined (type syscall.PtraceRegs has no field or method Rsi)
    pkg/ptrace/ptrace_linux.go:269:8: regs.Rdx undefined (type syscall.PtraceRegs has no field or method Rdx)
    pkg/ptrace/ptrace_linux.go:271:8: regs.R10 undefined (type syscall.PtraceRegs has no field or method R10)
    pkg/ptrace/ptrace_linux.go:273:8: regs.R8 undefined (type syscall.PtraceRegs has no field or method R8)
    pkg/ptrace/ptrace_linux.go:275:8: regs.R9 undefined (type syscall.PtraceRegs has no field or method R9)
    pkg/ptrace/ptrace_linux.go:289:61: p.backupRegs.Rip undefined (type *syscall.PtraceRegs has no field or method Rip)
    pkg/ptrace/ptrace_linux.go:289:61: too many errors
    

    查看对应的目录

    root@sjt-pc:/wk/github.com/chaos-mesh/chaos-mesh/pkg/ptrace# tree
    .
    ├── cwrapper_linux.go
    ├── ptrace_linux.go
    └── ptrace_linux_test.go
    

    参照 golang 的编译约束,这些文件仅在 linux 下生效,但是很明显在 mips 下报错了,说明该文件并不仅仅只有操作系统约束,还应该是架构约束,查看最新版本的 chaos-mesh 对应目录,果然已经加上了架构约束。

    root@sjt-pc:/wk/github.com/chaos-mesh/chaos-mesh/pkg/ptrace# tree
    .
    ├── cwrapper_linux.go
    ├── ptrace_linux_amd64.go
    └── ptrace_linux_test.go
    

    那么我们的问题就是写出 ptrace_linux_mips64le.go。

    定位

    以下分析仍然针对v1.0.3 版本,分析源码,找到这个文件中的与架构有关的部分,我将其分为四个部分。

    1. Ptrace 系统调用相关寄存器

    我们找到两段代码,可以看到 backupRegs 实际上是 *syscall.PtraceRegs。并且使用了其中的几个变量例如 Rip,Rax 等等。我们需要比对一下 amd64 和 mips64le 对这二者的实现有什么区别。

    func (p *TracedProgram) Syscall(number uint64, args ...uint64) (uint64, error) {
        ...
        var regs syscall.PtraceRegs
        regs.Rax = number
        for index, arg := range args {
            // All these registers are hard coded for x86 platform
            if index == 0 {
                regs.Rdi = arg
            } else if index == 1 {
                regs.Rsi = arg
            } else if index == 2 {
                regs.Rdx = arg
            } else if index == 3 {
                regs.R10 = arg
            } else if index == 4 {
                regs.R8 = arg
            } else if index == 5 {
                regs.R9 = arg
            } else {
                return 0, fmt.Errorf("too many arguments for a syscall")
            }
        }
    ...
        return regs.Rax, p.Restore()
    }
    

    2. PC 寄存器

    func (p *TracedProgram) Restore() error {
        err := syscall.PtraceSetRegs(p.pid, p.backupRegs)
        if err != nil {
            return errors.WithStack(err)
        }
    
        _, err = syscall.PtracePokeData(p.pid, uintptr(p.backupRegs.Rip), p.backupCode)
        if err != nil {
            return errors.WithStack(err)
        }
    
        return nil
    }
    
    

    3. 两个系统调用号

    func (p *TracedProgram) WriteSlice(addr uint64, buffer []byte) error {
        ...
        // process_vm_writev syscall number is 311
        _, _, errno := syscall.Syscall6(311, uintptr(p.pid), uintptr(unsafe.Pointer(&localIov)), uintptr(1), uintptr(unsafe.Pointer(&remoteIov)), uintptr(1), uintptr(0))
        ...
    }
    
    func (p *TracedProgram) ReadSlice(addr uint64, size uint64) (*[]byte, error) {
        ...
        // process_vm_readv syscall number is 310
        _, _, errno := syscall.Syscall6(310, uintptr(p.pid), uintptr(unsafe.Pointer(&localIov)), uintptr(1), uintptr(unsafe.Pointer(&remoteIov)), uintptr(1), uintptr(0))
        ...
    }
    
    

    4. 两个指令的机器码

    func (p *TracedProgram) JumpToFakeFunc(originAddr uint64, targetAddr uint64) error {
        instructions := make([]byte, 16)
    
        // mov rax, targetAddr;
        // jmp rax ;
        instructions[0] = 0x48
        instructions[1] = 0xb8
        binary.LittleEndian.PutUint64(instructions[2:10], targetAddr)
        instructions[10] = 0xff
        instructions[11] = 0xe0
    
        return p.PtraceWriteSlice(originAddr, instructions)
    }
    
    

    解决第一个问题,系统调用涉及的寄存器

    首先在 golang 源码中分别找到 amd64 和 mips64le 对 syscall.PtraceRegs 的定义,结合 Ptrace 的知识,这里是用户空间中可以访问的寄存器信息。

    ztypes_linux_amd64.go

    type PtraceRegs struct {
        R15      uint64
        R14      uint64
        R13      uint64
        R12      uint64
        Rbp      uint64
        Rbx      uint64
        R11      uint64
        R10      uint64
        R9       uint64
        R8       uint64
        Rax      uint64
        Rcx      uint64
        Rdx      uint64
        Rsi      uint64
        Rdi      uint64
        Orig_rax uint64
        Rip      uint64
        Cs       uint64
        Eflags   uint64
        Rsp      uint64
        Ss       uint64
        Fs_base  uint64
        Gs_base  uint64
        Ds       uint64
        Es       uint64
        Fs       uint64
        Gs       uint64
    }
    

    ztypes_linux_mips64le.go

    type PtraceRegs struct {
        Regs        [102]uint64
        U_tsize     uint64
        U_dsize     uint64
        U_ssize     uint64
        Start_code  uint64
        Start_data  uint64
        Start_stack uint64
        Signal      int64
        U_ar0       uint64
        Magic       uint64
        U_comm      [32]int8
    }
    

    结合系统调用的知识,在 amd64 下,系统调用就是将系统调用号放在 rax 寄存器中,然后将参数放在的 Rdi,Rsi,Rdx,R10,R8,R9 中,最后将返回值放在 rax 中。这是系统调用时参数传递的固定寄存器。
    那么我们就要知道在 mips64le 下,系统调用是怎么工作的。我们可以在 go 代码中找到系统调用 plan9 的汇编实现。从实现中,我们可以看到,mips64le 系统调用的参数传递使用了 R4-R9 这几个寄存器,同时调用号和返回值使用了 R2。

    src/syscall/asm_linux_amd64.s

    TEXT ·Syscall6(SB),NOSPLIT,$0-80
        CALL    runtime·entersyscall(SB)
        MOVQ    a1+8(FP), DI
        MOVQ    a2+16(FP), SI
        MOVQ    a3+24(FP), DX
        MOVQ    a4+32(FP), R10
        MOVQ    a5+40(FP), R8
        MOVQ    a6+48(FP), R9
        MOVQ    trap+0(FP), AX  // syscall entry
        SYSCALL
        CMPQ    AX, $0xfffffffffffff001
        JLS ok6
        MOVQ    $-1, r1+56(FP)
        MOVQ    $0, r2+64(FP)
        NEGQ    AX
        MOVQ    AX, err+72(FP)
        CALL    runtime·exitsyscall(SB)
        RET
    ok6:
        MOVQ    AX, r1+56(FP)
        MOVQ    DX, r2+64(FP)
        MOVQ    $0, err+72(FP)
        CALL    runtime·exitsyscall(SB)
        RET
    

    同理,我们可以找到mips64对于 syscall 的实现

    src/syscall/asm_linux_mips64.s

    TEXT ·Syscall6(SB),NOSPLIT,$0-80
        JAL runtime·entersyscall(SB)
        MOVV    a1+8(FP), R4
        MOVV    a2+16(FP), R5
        MOVV    a3+24(FP), R6
        MOVV    a4+32(FP), R7
        MOVV    a5+40(FP), R8
        MOVV    a6+48(FP), R9
        MOVV    trap+0(FP), R2  // syscall entry
        SYSCALL
        BEQ R7, ok6
        MOVV    $-1, R1
        MOVV    R1, r1+56(FP)   // r1
        MOVV    R0, r2+64(FP)   // r2
        MOVV    R2, err+72(FP)  // errno
        JAL runtime·exitsyscall(SB)
        RET
    ok6:
        MOVV    R2, r1+56(FP)   // r1
        MOVV    R3, r2+64(FP)   // r2
        MOVV    R0, err+72(FP)  // errno
        JAL runtime·exitsyscall(SB)
        RET
    

    此时,我们便找到了在 mips64 下 syscall 使用的几个寄存器了,就是四号到九号寄存器 R4-R9。

    第二个问题,解决 PC 寄存器

    rip 寄存器即 ip 寄存器,指令指针寄存器,用于记录下一个指令的地址。在 amd64 下,其直接写明了 RIP 寄存器。所以需要找到 mips64le 的指令指针寄存器。
    恰好在 src/syscall/syscall_linux_*.go 下,go 为我们提供了各个架构的 PC 寄存器的定义。

    root@sjt-pc:/wk/github.com/golang/go# grep -rn "SetPC"
    src/syscall/syscall_linux_mipsx.go:212:func (r *PtraceRegs) SetPC(pc uint64) { r.Regs[64] = uint32(pc) }
    src/syscall/syscall_linux_386.go:376:func (r *PtraceRegs) SetPC(pc uint64) { r.Eip = int32(pc) }
    src/syscall/syscall_linux_amd64.go:141:func (r *PtraceRegs) SetPC(pc uint64) { r.Rip = pc }
    src/syscall/syscall_linux_riscv64.go:178:func (r *PtraceRegs) SetPC(pc uint64) { r.Pc = pc }
    src/syscall/syscall_linux_mips64x.go:202:func (r *PtraceRegs) SetPC(pc uint64) { r.Regs[64] = pc }
    src/syscall/syscall_linux_arm.go:225:func (r *PtraceRegs) SetPC(pc uint64) { r.Uregs[15] = uint32(pc) }
    src/syscall/syscall_linux_s390x.go:283:func (r *PtraceRegs) SetPC(pc uint64) { r.Psw.Addr = pc }
    src/syscall/syscall_linux_ppc64x.go:110:func (r *PtraceRegs) SetPC(pc uint64) { r.Nip = pc }
    src/syscall/syscall_linux_arm64.go:193:func (r *PtraceRegs) SetPC(pc uint64) { r.Pc = pc }
    

    第三个问题,解决系统调用

    mips64le 的操作系统的系统调用号定义在 unistd.h 中,找到对于系统调用功能的调用号。 即 4345 和 4346

    #define __NR_process_vm_readv       (__NR_Linux + 345)
    #define __NR_process_vm_writev      (__NR_Linux + 346)
    
    

    第四个问题,解决两个指令的机器码

    func (p *TracedProgram) JumpToFakeFunc(originAddr uint64, targetAddr uint64) error {
        instructions := make([]byte, 16)
    
        // mov rax, targetAddr;
        // jmp rax ;
        instructions[0] = 0x48
        instructions[1] = 0xb8
        binary.LittleEndian.PutUint64(instructions[2:10], targetAddr)
        instructions[10] = 0xff
        instructions[11] = 0xe0
    
        return p.PtraceWriteSlice(originAddr, instructions)
    }
    

    首先理解汇编的含义,即将一个4字节数放入 rax 寄存器,然后跳转到该寄存器值所指向的地址。那么我们需要找到 mips64 下对应的指令,或者说机器码。参考 mips 指令 https://www.cnblogs.com/CoBrAMG/p/9237609.html
    我们可以使用 ld 指令加载一个64位数到寄存器中,然后使用 jr 跳转,如下。编译这段汇编代码,并查看其对应的机器码。可以看到 ld 指令被拆分成了好几条指令,因为 mips64 每条指令只有 32 位,所以将这个64位数分四次每次16位分别载入到64位寄存器中。

        ld $2, 0x1122334455667788
        jr $2
    
    
    [root@localhost test]# objdump -d main.o
    0000000000000000 <test>:
       0:   3c021122    lui v0,0x1122
       4:   34423344    ori v0,v0,0x3344
       8:   00021438    dsll    v0,v0,0x10
       c:   34425566    ori v0,v0,0x5566
      10:   00021438    dsll    v0,v0,0x10
      14:   dc427788    ld  v0,30600(v0)
      18:   00400008    jr  v0
      1c:   00000000    nop
    

    按照这个逻辑写出对应的机器码

    func (p *TracedProgram) JumpToFakeFunc(originAddr uint64, targetAddr uint64) error {
        instructions := make([]byte,28)
        var l1,l2,l3,l4 uint16
    
        l4 = uint16(targetAddr & 0xff)
        targetAddr = targetAddr >> 16
    
        l3 = uint16(targetAddr & 0xff)
        targetAddr = targetAddr >> 16
    
        l2 = uint16(targetAddr & 0xff)
        targetAddr = targetAddr >> 16
    
        l1 = uint16(targetAddr & 0xff)
    
        
        // lui v0,0x1122           
        instructions[0] = 0x3c     
        instructions[1] = 0x02
        binary.LittleEndian.PutUint16(instructions[2:4],l1)
    
        // ori v0,v0,0x3344        
        instructions[4] = 0x34
        instructions[5] = 0x42     
        binary.LittleEndian.PutUint16(instructions[6:8],l2)
        
        // dsll v0,v0,0x10         
        instructions[8] = 0x00
        instructions[9] = 0x02
        instructions[10] = 0x14    
        instructions[11] = 0x38    
    
        // ori v0,v0,0x5566        
        instructions[12] = 0x34    
        instructions[13] = 0x42
        binary.LittleEndian.PutUint16(instructions[14:16],l3)
    
        // dsll v0,v0,0x10
        instructions[16] = 0x00
        instructions[17] = 0x02
        instructions[18] = 0x14
        instructions[19] = 0x38
    
        // ld,v0,0x7788
        instructions[20] =  0xdc
        instructions[21] =  0x42
        binary.LittleEndian.PutUint16(instructions[22:24],l4)
    
        // jr v0
        instructions[24] = 0x00
        instructions[25] = 0x40
        instructions[26] = 0x00
        instructions[27] = 0x08
    
        return p.PtraceWriteSlice(originAddr, instructions)
    }
    
    

    相关文章

      网友评论

          本文标题:chaos-mesh on mips64

          本文链接:https://www.haomeiwen.com/subject/esptfltx.html