github.com/peggyl/go@v0.0.0-20151008231540-ae315999c2d5/src/cmd/newlink/macho_test.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "bytes" 9 "debug/macho" 10 "encoding/binary" 11 "fmt" 12 "io/ioutil" 13 "strings" 14 "testing" 15 ) 16 17 // Test macho writing by checking that each generated prog can be written 18 // and then read back using debug/macho to get the same prog. 19 // Also check against golden testdata file. 20 var machoWriteTests = []struct { 21 name string 22 golden bool 23 prog *Prog 24 }{ 25 // amd64 exit 9 26 { 27 name: "exit9", 28 golden: true, 29 prog: &Prog{ 30 GOARCH: "amd64", 31 GOOS: "darwin", 32 UnmappedSize: 0x1000, 33 Entry: 0x1000, 34 Segments: []*Segment{ 35 { 36 Name: "text", 37 VirtAddr: 0x1000, 38 VirtSize: 13, 39 FileOffset: 0, 40 FileSize: 13, 41 Data: []byte{ 42 0xb8, 0x01, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX 43 0xbf, 0x09, 0x00, 0x00, 0x00, // MOVL $9, DI 44 0x0f, 0x05, // SYSCALL 45 0xf4, // HLT 46 }, 47 Sections: []*Section{ 48 { 49 Name: "text", 50 VirtAddr: 0x1000, 51 Size: 13, 52 Align: 64, 53 }, 54 }, 55 }, 56 }, 57 }, 58 }, 59 60 // amd64 write hello world & exit 9 61 { 62 name: "hello", 63 golden: true, 64 prog: &Prog{ 65 GOARCH: "amd64", 66 GOOS: "darwin", 67 UnmappedSize: 0x1000, 68 Entry: 0x1000, 69 Segments: []*Segment{ 70 { 71 Name: "text", 72 VirtAddr: 0x1000, 73 VirtSize: 35, 74 FileOffset: 0, 75 FileSize: 35, 76 Data: []byte{ 77 0xb8, 0x04, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX 78 0xbf, 0x01, 0x00, 0x00, 0x00, // MOVL $1, DI 79 0xbe, 0x00, 0x30, 0x00, 0x00, // MOVL $0x3000, SI 80 0xba, 0x0c, 0x00, 0x00, 0x00, // MOVL $12, DX 81 0x0f, 0x05, // SYSCALL 82 0xb8, 0x01, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX 83 0xbf, 0x09, 0x00, 0x00, 0x00, // MOVL $9, DI 84 0x0f, 0x05, // SYSCALL 85 0xf4, // HLT 86 }, 87 Sections: []*Section{ 88 { 89 Name: "text", 90 VirtAddr: 0x1000, 91 Size: 35, 92 Align: 64, 93 }, 94 }, 95 }, 96 { 97 Name: "data", 98 VirtAddr: 0x2000, 99 VirtSize: 12, 100 FileOffset: 0x1000, 101 FileSize: 12, 102 Data: []byte("hello world\n"), 103 Sections: []*Section{ 104 { 105 Name: "data", 106 VirtAddr: 0x2000, 107 Size: 12, 108 Align: 64, 109 }, 110 }, 111 }, 112 }, 113 }, 114 }, 115 116 // amd64 write hello world from rodata & exit 0 117 { 118 name: "helloro", 119 golden: true, 120 prog: &Prog{ 121 GOARCH: "amd64", 122 GOOS: "darwin", 123 UnmappedSize: 0x1000, 124 Entry: 0x1000, 125 Segments: []*Segment{ 126 { 127 Name: "text", 128 VirtAddr: 0x1000, 129 VirtSize: 0x100c, 130 FileOffset: 0, 131 FileSize: 0x100c, 132 Data: concat( 133 []byte{ 134 0xb8, 0x04, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX 135 0xbf, 0x01, 0x00, 0x00, 0x00, // MOVL $1, DI 136 0xbe, 0x00, 0x30, 0x00, 0x00, // MOVL $0x3000, SI 137 0xba, 0x0c, 0x00, 0x00, 0x00, // MOVL $12, DX 138 0x0f, 0x05, // SYSCALL 139 0xb8, 0x01, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX 140 0xbf, 0x00, 0x00, 0x00, 0x00, // MOVL $0, DI 141 0x0f, 0x05, // SYSCALL 142 0xf4, // HLT 143 }, 144 make([]byte, 0x1000-35), 145 []byte("hello world\n"), 146 ), 147 Sections: []*Section{ 148 { 149 Name: "text", 150 VirtAddr: 0x1000, 151 Size: 35, 152 Align: 64, 153 }, 154 { 155 Name: "rodata", 156 VirtAddr: 0x2000, 157 Size: 12, 158 Align: 64, 159 }, 160 }, 161 }, 162 }, 163 }, 164 }, 165 } 166 167 func concat(xs ...[]byte) []byte { 168 var out []byte 169 for _, x := range xs { 170 out = append(out, x...) 171 } 172 return out 173 } 174 175 func TestMachoWrite(t *testing.T) { 176 for _, tt := range machoWriteTests { 177 name := tt.prog.GOARCH + "." + tt.name 178 prog := cloneProg(tt.prog) 179 prog.init() 180 var f machoFormat 181 vsize, fsize := f.headerSize(prog) 182 shiftProg(prog, vsize, fsize) 183 var buf bytes.Buffer 184 f.write(&buf, prog) 185 if false { // enable to debug 186 ioutil.WriteFile("a.out", buf.Bytes(), 0777) 187 } 188 read, err := machoRead(machoArches[tt.prog.GOARCH], buf.Bytes()) 189 if err != nil { 190 t.Errorf("%s: reading mach-o output:\n\t%v", name, err) 191 continue 192 } 193 diffs := diffProg(read, prog) 194 if diffs != nil { 195 t.Errorf("%s: mismatched prog:\n\t%s", name, strings.Join(diffs, "\n\t")) 196 continue 197 } 198 if !tt.golden { 199 continue 200 } 201 checkGolden(t, buf.Bytes(), "testdata/macho."+name) 202 } 203 } 204 205 // machoRead reads the mach-o file in data and returns a corresponding prog. 206 func machoRead(arch machoArch, data []byte) (*Prog, error) { 207 f, err := macho.NewFile(bytes.NewReader(data)) 208 if err != nil { 209 return nil, err 210 } 211 212 var errors []string 213 errorf := func(format string, args ...interface{}) { 214 errors = append(errors, fmt.Sprintf(format, args...)) 215 } 216 217 magic := uint32(0xFEEDFACE) 218 if arch.CPU&macho64Bit != 0 { 219 magic |= 1 220 } 221 if f.Magic != magic { 222 errorf("header: Magic = %#x, want %#x", f.Magic, magic) 223 } 224 if f.Cpu != macho.CpuAmd64 { 225 errorf("header: CPU = %#x, want %#x", f.Cpu, macho.CpuAmd64) 226 } 227 if f.SubCpu != 3 { 228 errorf("header: SubCPU = %#x, want %#x", f.SubCpu, 3) 229 } 230 if f.Type != 2 { 231 errorf("header: FileType = %d, want %d", f.Type, 2) 232 } 233 if f.Flags != 1 { 234 errorf("header: Flags = %d, want %d", f.Flags, 1) 235 } 236 237 msects := f.Sections 238 var limit uint64 239 prog := new(Prog) 240 for _, load := range f.Loads { 241 switch load := load.(type) { 242 default: 243 errorf("unexpected macho load %T %x", load, load.Raw()) 244 245 case macho.LoadBytes: 246 if len(load) < 8 || len(load)%4 != 0 { 247 errorf("unexpected load length %d", len(load)) 248 continue 249 } 250 cmd := f.ByteOrder.Uint32(load) 251 switch macho.LoadCmd(cmd) { 252 default: 253 errorf("unexpected macho load cmd %s", macho.LoadCmd(cmd)) 254 case macho.LoadCmdUnixThread: 255 data := make([]uint32, len(load[8:])/4) 256 binary.Read(bytes.NewReader(load[8:]), f.ByteOrder, data) 257 if len(data) != 44 { 258 errorf("macho thread len(data) = %d, want 42", len(data)) 259 continue 260 } 261 if data[0] != 4 { 262 errorf("macho thread type = %d, want 4", data[0]) 263 } 264 if data[1] != uint32(len(data))-2 { 265 errorf("macho thread desc len = %d, want %d", data[1], uint32(len(data))-2) 266 continue 267 } 268 for i, val := range data[2:] { 269 switch i { 270 default: 271 if val != 0 { 272 errorf("macho thread data[%d] = %#x, want 0", i, val) 273 } 274 case 32: 275 prog.Entry = Addr(val) 276 case 33: 277 prog.Entry |= Addr(val) << 32 278 } 279 } 280 } 281 282 case *macho.Segment: 283 if load.Addr < limit { 284 errorf("segments out of order: %q at %#x after %#x", load.Name, load.Addr, limit) 285 } 286 limit = load.Addr + load.Memsz 287 if load.Name == "__PAGEZERO" || load.Addr == 0 && load.Filesz == 0 { 288 if load.Name != "__PAGEZERO" { 289 errorf("segment with Addr=0, Filesz=0 is named %q, want %q", load.Name, "__PAGEZERO") 290 } else if load.Addr != 0 || load.Filesz != 0 { 291 errorf("segment %q has Addr=%#x, Filesz=%d, want Addr=%#x, Filesz=%d", load.Name, load.Addr, load.Filesz, 0, 0) 292 } 293 prog.UnmappedSize = Addr(load.Memsz) 294 continue 295 } 296 297 if !strings.HasPrefix(load.Name, "__") { 298 errorf("segment name %q does not begin with %q", load.Name, "__") 299 } 300 if strings.ToUpper(load.Name) != load.Name { 301 errorf("segment name %q is not all upper case", load.Name) 302 } 303 304 seg := &Segment{ 305 Name: strings.ToLower(strings.TrimPrefix(load.Name, "__")), 306 VirtAddr: Addr(load.Addr), 307 VirtSize: Addr(load.Memsz), 308 FileOffset: Addr(load.Offset), 309 FileSize: Addr(load.Filesz), 310 } 311 prog.Segments = append(prog.Segments, seg) 312 313 data, err := load.Data() 314 if err != nil { 315 errorf("loading data from %q: %v", load.Name, err) 316 } 317 seg.Data = data 318 319 var maxprot, prot uint32 320 if load.Name == "__TEXT" { 321 maxprot, prot = 7, 5 322 } else { 323 maxprot, prot = 3, 3 324 } 325 if load.Maxprot != maxprot || load.Prot != prot { 326 errorf("segment %q protection is %d, %d, want %d, %d", 327 load.Name, load.Maxprot, load.Prot, maxprot, prot) 328 } 329 330 for len(msects) > 0 && msects[0].Addr < load.Addr+load.Memsz { 331 msect := msects[0] 332 msects = msects[1:] 333 334 if msect.Offset > 0 && prog.HeaderSize == 0 { 335 prog.HeaderSize = Addr(msect.Offset) 336 if seg.FileOffset != 0 { 337 errorf("initial segment %q does not map header", load.Name) 338 } 339 seg.VirtAddr += prog.HeaderSize 340 seg.VirtSize -= prog.HeaderSize 341 seg.FileOffset += prog.HeaderSize 342 seg.FileSize -= prog.HeaderSize 343 seg.Data = seg.Data[prog.HeaderSize:] 344 } 345 346 if msect.Addr < load.Addr { 347 errorf("section %q at address %#x is missing segment", msect.Name, msect.Addr) 348 continue 349 } 350 351 if !strings.HasPrefix(msect.Name, "__") { 352 errorf("section name %q does not begin with %q", msect.Name, "__") 353 } 354 if strings.ToLower(msect.Name) != msect.Name { 355 errorf("section name %q is not all lower case", msect.Name) 356 } 357 if msect.Seg != load.Name { 358 errorf("section %q is lists segment name %q, want %q", 359 msect.Name, msect.Seg, load.Name) 360 } 361 if uint64(msect.Offset) != uint64(load.Offset)+msect.Addr-load.Addr { 362 errorf("section %q file offset is %#x, want %#x", 363 msect.Name, msect.Offset, load.Offset+msect.Addr-load.Addr) 364 } 365 if msect.Reloff != 0 || msect.Nreloc != 0 { 366 errorf("section %q has reloff %d,%d, want %d,%d", 367 msect.Name, msect.Reloff, msect.Nreloc, 0, 0) 368 } 369 flags := uint32(0) 370 if msect.Name == "__text" { 371 flags = 0x400 372 } 373 if msect.Offset == 0 { 374 flags = 1 375 } 376 if msect.Flags != flags { 377 errorf("section %q flags = %#x, want %#x", msect.Name, msect.Flags, flags) 378 } 379 sect := &Section{ 380 Name: strings.ToLower(strings.TrimPrefix(msect.Name, "__")), 381 VirtAddr: Addr(msect.Addr), 382 Size: Addr(msect.Size), 383 Align: 1 << msect.Align, 384 } 385 seg.Sections = append(seg.Sections, sect) 386 } 387 } 388 } 389 390 for _, msect := range msects { 391 errorf("section %q has no segment", msect.Name) 392 } 393 394 limit = 0 395 for _, msect := range f.Sections { 396 if msect.Addr < limit { 397 errorf("sections out of order: %q at %#x after %#x", msect.Name, msect.Addr, limit) 398 } 399 limit = msect.Addr + msect.Size 400 } 401 402 err = nil 403 if errors != nil { 404 err = fmt.Errorf("%s", strings.Join(errors, "\n\t")) 405 } 406 return prog, err 407 }