lunixbochs / usercorn

dynamic binary analysis via platform emulation

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

unstable: MemBatch is broken

lunixbochs opened this issue · comments

0x8875271: rep movsd dword ptr es:[edi], dword ptr [esi]     |    ecx = 0x00000000 | R 888466a
                                                             +    edi = 0xbffff373 + W bffff363
                                                             +    esi = 0x0888467a + R 888466e
R 0x08884668: 7573                                           [us                  ] R
W 0xbffff361: 7573722f 6c69                                  [usr/li              ] W
R 0x0888466a: 722f6c69                                       [r/li                ] R
R 0x0888466e: 622f6c69                                       [b/li                ] R
W 0xbffff367: 622f6c69                                       [b/li                ] W
R 0x08884672: 62632e73                                       [bc.s                ] R
W 0xbffff36b: 62632e73                                       [bc.s                ] W
R 0x08884676: 6f2e3600                                       [o.6.                ] R
W 0xbffff36f: 6f2e3600                                       [o.6.                ] W

Note the overlapping IO that should be batched here.

It seems to be because rep triggers three ops on each: A read, a write, and a jmp. The jmp triggers the Flush.

Simple ELF to test:

python -c "print '7f454c4601010100000000000000000002000300010000007490040834000000a400000000000000340020000200280003000200010000000000000000900408009004088c0000008c000000070000000010000051e5746400000000000000000000000000000000000000000700000010000000b90400000089e783ef4089e6f3a531dbb801000000cd8000002e7368737472746162002e7368656c6c636f6465000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000010000000700000074900408740000001800000000000000000000000100000000000000010000000300000000000000000000008c0000001600000000000000000000000100000000000000'.decode('hex')" > x

Looks exactly right, here's a partial patch detecting self-jmp (which still inadvertently flushes on the first rep invocation):

diff --git a/go/models/trace/filter_membatch.go b/go/models/trace/filter_membatch.go
index 80c9e68..d1f3d94 100644
--- a/go/models/trace/filter_membatch.go
+++ b/go/models/trace/filter_membatch.go
@@ -42,12 +42,24 @@ func (o *OpMemBatch) Render(mem *cpu.Mem) string {
 
 type MemBatch struct {
        memOps []models.Op
+       pc     uint64
+       lastPC uint64
 }
 
 func (m *MemBatch) Filter(op models.Op) []models.Op {
-       switch op.(type) {
+       switch v := op.(type) {
        case *OpJmp:
-               return append(m.Flush(), op)
+               fmt.Printf("jmp: m.pc=%#x m.lastPC=%#x jmp.addr=%#x\n", m.pc, m.lastPC, v.Addr)
+               selfJmp := v.Addr == m.lastPC
+               m.lastPC = m.pc
+               m.pc = v.Addr
+               if !selfJmp {
+                       return append(m.Flush(), op)
+               }
+       case *OpStep:
+               fmt.Printf("step: m.pc=%#x m.lastPC=%#x step.size=%d\n", m.pc, m.lastPC, v.Size)
+               m.lastPC = m.pc
+               m.pc += uint64(v.Size)
        case *OpMemRead, *OpMemWrite:
                m.memOps = append(m.memOps, op)
        }
@@ -55,6 +67,7 @@ func (m *MemBatch) Filter(op models.Op) []models.Op {
 }
 
 func (m *MemBatch) Flush() []models.Op {
+       fmt.Println("membatch flush")
        log := &MemLog{}
 
        // Build a log of all reads and writes

Output:

membatch flush
jmp: m.pc=0x0 m.lastPC=0x0 jmp.addr=0x8049074
membatch flush
step: m.pc=0x8049074 m.lastPC=0x0 step.size=5
0x8049074: mov ecx, 4                                        |    ecx = 0x00000004
step: m.pc=0x8049079 m.lastPC=0x8049074 step.size=2
0x8049079: mov edi, esp                                      |    edi = 0xbffff9c8
step: m.pc=0x804907b m.lastPC=0x8049079 step.size=3
0x804907b: sub edi, 0x40                                     |    edi = 0xbffff988
                                                             + eflags = 0x00000084
step: m.pc=0x804907e m.lastPC=0x804907b step.size=2
0x804907e: mov esi, esp                                      |    esi = 0xbffff9c8
jmp: m.pc=0x8049080 m.lastPC=0x804907e jmp.addr=0x8049080
membatch flush
jmp: m.pc=0x8049080 m.lastPC=0x8049080 jmp.addr=0x8049080
jmp: m.pc=0x8049080 m.lastPC=0x8049080 jmp.addr=0x8049080
jmp: m.pc=0x8049080 m.lastPC=0x8049080 jmp.addr=0x8049080
step: m.pc=0x8049080 m.lastPC=0x8049080 step.size=2
0x8049080: rep movsd dword ptr es:[edi], dword ptr [esi]     |    ecx = 0x00000000 | R bffff9c8
                                                             +    edi = 0xbffff998 + W bffff988
                                                             +    esi = 0xbffff9d8 + R bffff9cc
R 0xbffff9c8: 00000000                                       [....                ] R
W 0xbffff988: 01000000                                       [....                ] W
jmp: m.pc=0x8049082 m.lastPC=0x8049080 jmp.addr=0x8049082
membatch flush
step: m.pc=0x8049082 m.lastPC=0x8049082 step.size=2
0x8049082: xor ebx, ebx                                      | eflags = 0x00000044
R 0xbffff9cc: 00000000                                       [....                ] R
W 0xbffff98c: e9faffbf 00000000 c3ffffbf                     [............        ] W
R 0xbffff9d0: 00000000                                       [....                ] R
R 0xbffff9d4: 00000000                                       [....                ] R
step: m.pc=0x8049084 m.lastPC=0x8049082 step.size=5
0x8049084: mov eax, 1                                        |    eax = 0x00000001
step: m.pc=0x8049089 m.lastPC=0x8049084 step.size=2

This seems to patch it but doesn't flush on exit, which is a newly found bug:

diff --git a/go/models/trace/filter_membatch.go b/go/models/trace/filter_membatch.go
index 80c9e68..786fe5e 100644
--- a/go/models/trace/filter_membatch.go
+++ b/go/models/trace/filter_membatch.go
@@ -42,12 +42,21 @@ func (o *OpMemBatch) Render(mem *cpu.Mem) string {
 
 type MemBatch struct {
        memOps []models.Op
+       pc     uint64
 }
 
 func (m *MemBatch) Filter(op models.Op) []models.Op {
-       switch op.(type) {
+       switch v := op.(type) {
        case *OpJmp:
-               return append(m.Flush(), op)
+               fmt.Printf("jmp: m.pc=%#x jmp.addr=%#x\n", m.pc, v.Addr)
+               selfJmp := v.Addr == m.pc
+               m.pc = v.Addr
+               if !selfJmp {
+                       return append(m.Flush(), op)
+               }
+       case *OpStep:
+               fmt.Printf("step: m.pc=%#x step.size=%d\n", m.pc, v.Size)
+               m.pc += uint64(v.Size)
        case *OpMemRead, *OpMemWrite:
                m.memOps = append(m.memOps, op)
        }
@@ -55,6 +64,7 @@ func (m *MemBatch) Filter(op models.Op) []models.Op {
 }
 
 func (m *MemBatch) Flush() []models.Op {
+       fmt.Println("membatch flush")
        log := &MemLog{}
 
        // Build a log of all reads and writes

fixed in unstable, wow that shook out a lot of tracing errata

💥