debug/dwarf: add unit tests for line table reader

This adds simple ELF test binaries generated by gcc and clang and
compares the line tables returned by the line table reader against
tables based on the output of readelf.

The binaries were generated with
    # gcc --version | head -n1
    gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
    # gcc -g -o line-gcc.elf line*.c

    # clang --version | head -n1
    Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
    # clang -g -o line-clang.elf line*.c

Change-Id: Id210fdc1d007ac9719e8f5dc845f2b94eed12234
Reviewed-on: https://go-review.googlesource.com/7070
Reviewed-by: Nigel Tao <nigeltao@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
Austin Clements 2015-03-06 13:26:04 -05:00
parent 3393009267
commit fdd49d2be0
6 changed files with 251 additions and 0 deletions

View File

@ -0,0 +1,229 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package dwarf_test
import (
. "debug/dwarf"
"io"
"testing"
)
var (
file1C = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line1.c"}
file1H = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line1.h"}
file2C = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line2.c"}
)
func TestLineELFGCC(t *testing.T) {
// Generated by:
// # gcc --version | head -n1
// gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
// # gcc -g -o line-gcc.elf line*.c
// Line table based on readelf --debug-dump=rawline,decodedline
want := []LineEntry{
{Address: 0x40059d, File: file1H, Line: 2, IsStmt: true},
{Address: 0x4005a5, File: file1H, Line: 2, IsStmt: true},
{Address: 0x4005b4, File: file1H, Line: 5, IsStmt: true},
{Address: 0x4005bd, File: file1H, Line: 6, IsStmt: true, Discriminator: 2},
{Address: 0x4005c7, File: file1H, Line: 5, IsStmt: true, Discriminator: 2},
{Address: 0x4005cb, File: file1H, Line: 5, IsStmt: false, Discriminator: 1},
{Address: 0x4005d1, File: file1H, Line: 7, IsStmt: true},
{Address: 0x4005e7, File: file1C, Line: 6, IsStmt: true},
{Address: 0x4005eb, File: file1C, Line: 7, IsStmt: true},
{Address: 0x4005f5, File: file1C, Line: 8, IsStmt: true},
{Address: 0x4005ff, File: file1C, Line: 9, IsStmt: true},
{Address: 0x400601, EndSequence: true},
{Address: 0x400601, File: file2C, Line: 4, IsStmt: true},
{Address: 0x400605, File: file2C, Line: 5, IsStmt: true},
{Address: 0x40060f, File: file2C, Line: 6, IsStmt: true},
{Address: 0x400611, EndSequence: true},
}
testLineTable(t, want, elfData(t, "testdata/line-gcc.elf"))
}
func TestLineELFClang(t *testing.T) {
// Generated by:
// # clang --version | head -n1
// Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
// # clang -g -o line-clang.elf line*.
want := []LineEntry{
{Address: 0x400530, File: file1C, Line: 6, IsStmt: true},
{Address: 0x400534, File: file1C, Line: 7, IsStmt: true, PrologueEnd: true},
{Address: 0x400539, File: file1C, Line: 8, IsStmt: true},
{Address: 0x400545, File: file1C, Line: 9, IsStmt: true},
{Address: 0x400550, File: file1H, Line: 2, IsStmt: true},
{Address: 0x400554, File: file1H, Line: 5, IsStmt: true, PrologueEnd: true},
{Address: 0x400568, File: file1H, Line: 6, IsStmt: true},
{Address: 0x400571, File: file1H, Line: 5, IsStmt: true},
{Address: 0x400581, File: file1H, Line: 7, IsStmt: true},
{Address: 0x400583, EndSequence: true},
{Address: 0x400590, File: file2C, Line: 4, IsStmt: true},
{Address: 0x4005a0, File: file2C, Line: 5, IsStmt: true, PrologueEnd: true},
{Address: 0x4005a7, File: file2C, Line: 6, IsStmt: true},
{Address: 0x4005b0, EndSequence: true},
}
testLineTable(t, want, elfData(t, "testdata/line-clang.elf"))
}
func TestLineSeek(t *testing.T) {
d := elfData(t, "testdata/line-gcc.elf")
// Get the line table for the first CU.
cu, err := d.Reader().Next()
if err != nil {
t.Fatal("d.Reader().Next:", err)
}
lr, err := d.LineReader(cu)
if err != nil {
t.Fatal("d.LineReader:", err)
}
// Read entries forward.
var line LineEntry
var posTable []LineReaderPos
var table []LineEntry
for {
posTable = append(posTable, lr.Tell())
err := lr.Next(&line)
if err != nil {
if err == io.EOF {
break
}
t.Fatal("lr.Next:", err)
}
table = append(table, line)
}
// Test that Reset returns to the first line.
lr.Reset()
if err := lr.Next(&line); err != nil {
t.Fatal("lr.Next after Reset failed:", err)
} else if line != table[0] {
t.Fatal("lr.Next after Reset returned", line, "instead of", table[0])
}
// Check that entries match when seeking backward.
for i := len(posTable) - 1; i >= 0; i-- {
lr.Seek(posTable[i])
err := lr.Next(&line)
if i == len(posTable)-1 {
if err != io.EOF {
t.Fatal("expected io.EOF after seek to end, got", err)
}
} else if err != nil {
t.Fatal("lr.Next after seek to", posTable[i], "failed:", err)
} else if line != table[i] {
t.Fatal("lr.Next after seek to", posTable[i], "returned", line, "instead of", table[i])
}
}
// Check that seeking to a PC returns the right line.
if err := lr.SeekPC(table[0].Address-1, &line); err != ErrUnknownPC {
t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", table[0].Address-1, err)
}
for i, testLine := range table {
if testLine.EndSequence {
if err := lr.SeekPC(testLine.Address, &line); err != ErrUnknownPC {
t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", testLine.Address, err)
}
continue
}
nextPC := table[i+1].Address
for pc := testLine.Address; pc < nextPC; pc++ {
if err := lr.SeekPC(pc, &line); err != nil {
t.Fatalf("lr.SeekPC to %#x failed: %v", pc, err)
} else if line != testLine {
t.Fatalf("lr.SeekPC to %#x returned %v instead of %v", pc, line, testLine)
}
}
}
}
func testLineTable(t *testing.T, want []LineEntry, d *Data) {
// Get line table from d.
var got []LineEntry
dr := d.Reader()
for {
ent, err := dr.Next()
if err != nil {
t.Fatal("dr.Next:", err)
} else if ent == nil {
break
}
if ent.Tag != TagCompileUnit {
dr.SkipChildren()
continue
}
// Decode CU's line table.
lr, err := d.LineReader(ent)
if err != nil {
t.Fatal("d.LineReader:", err)
} else if lr == nil {
continue
}
for {
var line LineEntry
err := lr.Next(&line)
if err != nil {
if err == io.EOF {
break
}
t.Fatal("lr.Next:", err)
}
got = append(got, line)
}
}
// Compare line tables.
if !compareLines(got, want) {
t.Log("Line tables do not match. Got:")
dumpLines(t, got)
t.Log("Want:")
dumpLines(t, want)
t.FailNow()
}
}
func compareLines(a, b []LineEntry) bool {
if len(a) != len(b) {
return false
}
for i := range a {
al, bl := a[i], b[i]
// If both are EndSequence, then the only other valid
// field is Address. Otherwise, test equality of all
// fields.
if al.EndSequence && bl.EndSequence && al.Address == bl.Address {
continue
}
if al.File.Name != bl.File.Name {
return false
}
al.File = nil
bl.File = nil
if al != bl {
return false
}
}
return true
}
func dumpLines(t *testing.T, lines []LineEntry) {
for _, l := range lines {
t.Logf(" %+v File:%+v", l, l.File)
}
}

BIN
src/debug/dwarf/testdata/line-clang.elf vendored Executable file

Binary file not shown.

BIN
src/debug/dwarf/testdata/line-gcc.elf vendored Executable file

Binary file not shown.

9
src/debug/dwarf/testdata/line1.c vendored Normal file
View File

@ -0,0 +1,9 @@
#include "line1.h"
void f2();
int main()
{
f1();
f2();
}

7
src/debug/dwarf/testdata/line1.h vendored Normal file
View File

@ -0,0 +1,7 @@
static void f1()
{
char buf[10];
int i;
for(i = 0; i < 10; i++)
buf[i] = 1;
}

6
src/debug/dwarf/testdata/line2.c vendored Normal file
View File

@ -0,0 +1,6 @@
#include <stdio.h>
void f2()
{
printf("hello\n");
}