move out c/cpp/py
This commit is contained in:
115
_lldb/README.md
Normal file
115
_lldb/README.md
Normal file
@@ -0,0 +1,115 @@
|
||||
## LLGo Plugin of LLDB
|
||||
|
||||
### Build with debug info
|
||||
|
||||
```shell
|
||||
LLGO_DEBUG_SYMBOLS=1 llgo build -o cl/_testdata/debug/out ./cl/_testdata/debug
|
||||
```
|
||||
|
||||
### Debug with lldb
|
||||
|
||||
```shell
|
||||
_lldb/runlldb.sh ./cl/_testdata/debug/out
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```shell
|
||||
/opt/homebrew/bin/lldb -O "command script import _lldb/llgo_plugin.py" ./cl/_testdata/debug/out
|
||||
# github.com/goplus/llgo/cl/_testdata/debug
|
||||
Breakpoint 1: no locations (pending).
|
||||
Breakpoint set in dummy target, will get copied into future targets.
|
||||
(lldb) command script import _lldb/llgo_plugin.py
|
||||
(lldb) target create "./cl/_testdata/debug/out"
|
||||
Current executable set to '/Users/lijie/source/goplus/llgo/cl/_testdata/debug/out' (arm64).
|
||||
(lldb) r
|
||||
Process 21992 launched: '/Users/lijie/source/goplus/llgo/cl/_testdata/debug/out' (arm64)
|
||||
globalInt: 301
|
||||
s: 0x100123e40
|
||||
0x100123be0
|
||||
5 8
|
||||
called function with struct
|
||||
1 2 3 4 5 6 7 8 9 10 +1.100000e+01 +1.200000e+01 true (+1.300000e+01+1.400000e+01i) (+1.500000e+01+1.600000e+01i) [3/3]0x1001129a0 [3/3]0x100112920 hello 0x1001149b0 0x100123ab0 0x100123d10 0x1001149e0 (0x100116810,0x1001149d0) 0x10011bf00 0x10010fa80 (0x100116840,0x100112940) 0x10001b4a4
|
||||
9
|
||||
1 (0x1001167e0,0x100112900)
|
||||
called function with types
|
||||
0x100123e40
|
||||
0x1000343d0
|
||||
Process 21992 stopped
|
||||
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
|
||||
frame #0: 0x000000010001b3b4 out`main at in.go:225:12
|
||||
222 // s.i8: '\x01'
|
||||
223 // s.i16: 2
|
||||
224 s.i8 = 0x12
|
||||
-> 225 println(s.i8)
|
||||
226 // Expected:
|
||||
227 // all variables: globalInt globalStruct globalStructPtr s i err
|
||||
228 // s.i8: '\x12'
|
||||
(lldb) v
|
||||
var i int = <variable not available>
|
||||
var s github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = {
|
||||
i8 = '\x12',
|
||||
i16 = 2,
|
||||
i32 = 3,
|
||||
i64 = 4,
|
||||
i = 5,
|
||||
u8 = '\x06',
|
||||
u16 = 7,
|
||||
u32 = 8,
|
||||
u64 = 9,
|
||||
u = 10,
|
||||
f32 = 11,
|
||||
f64 = 12,
|
||||
b = true,
|
||||
c64 = {real = 13, imag = 14},
|
||||
c128 = {real = 15, imag = 16},
|
||||
slice = []int{21, 22, 23},
|
||||
arr = [3]int{24, 25, 26},
|
||||
arr2 = [3]github.com/goplus/llgo/cl/_testdata/debug.E{{i = 27}, {i = 28}, {i = 29}},
|
||||
s = "hello",
|
||||
e = {i = 30},
|
||||
pf = 0x0000000100123d10,
|
||||
pi = 0x00000001001149e0,
|
||||
intr = {type = 0x0000000100116810, data = 0x00000001001149d0},
|
||||
m = {count = 4296130304},
|
||||
c = {},
|
||||
err = {type = 0x0000000100116840, data = 0x0000000100112940},
|
||||
fn = {f = 0x000000010001b4a4, data = 0x00000001001149c0},
|
||||
pad1 = 100,
|
||||
pad2 = 200
|
||||
}
|
||||
var globalStructPtr *github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = <variable not available>
|
||||
var globalStruct github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = {
|
||||
i8 = '\x01',
|
||||
i16 = 2,
|
||||
i32 = 3,
|
||||
i64 = 4,
|
||||
i = 5,
|
||||
u8 = '\x06',
|
||||
u16 = 7,
|
||||
u32 = 8,
|
||||
u64 = 9,
|
||||
u = 10,
|
||||
f32 = 11,
|
||||
f64 = 12,
|
||||
b = true,
|
||||
c64 = {real = 13, imag = 14},
|
||||
c128 = {real = 15, imag = 16},
|
||||
slice = []int{21, 22, 23},
|
||||
arr = [3]int{24, 25, 26},
|
||||
arr2 = [3]github.com/goplus/llgo/cl/_testdata/debug.E{{i = 27}, {i = 28}, {i = 29}},
|
||||
s = "hello",
|
||||
e = {i = 30},
|
||||
pf = 0x0000000100123d10,
|
||||
pi = 0x00000001001149e0,
|
||||
intr = {type = 0x0000000100116810, data = 0x00000001001149d0},
|
||||
m = {count = 4296130304},
|
||||
c = {},
|
||||
err = {type = 0x0000000100116840, data = 0x0000000100112940},
|
||||
fn = {f = 0x000000010001b4a4, data = 0x00000001001149c0},
|
||||
pad1 = 100,
|
||||
pad2 = 200
|
||||
}
|
||||
var globalInt int = 301
|
||||
var err error = {type = 0x0000000100112900, data = 0x000000000000001a}
|
||||
```
|
||||
54
_lldb/common.sh
Normal file
54
_lldb/common.sh
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to find LLDB 18+
|
||||
find_lldb() {
|
||||
local lldb_paths=(
|
||||
"/opt/homebrew/bin/lldb"
|
||||
"/usr/local/bin/lldb"
|
||||
"/usr/bin/lldb"
|
||||
"lldb" # This will use the system PATH
|
||||
)
|
||||
|
||||
for lldb_path in "${lldb_paths[@]}"; do
|
||||
if command -v "$lldb_path" >/dev/null 2>&1; then
|
||||
local version
|
||||
version=$("$lldb_path" --version | grep -oE '[0-9]+' | head -1)
|
||||
if [ "$version" -ge 18 ]; then
|
||||
echo "$lldb_path"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Error: LLDB 18 or higher not found" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Find LLDB 18+
|
||||
LLDB_PATH=$(find_lldb)
|
||||
echo "LLDB_PATH: $LLDB_PATH"
|
||||
$LLDB_PATH --version
|
||||
export LLDB_PATH
|
||||
|
||||
# Default package path
|
||||
export DEFAULT_PACKAGE_PATH="./_lldb/lldbtest"
|
||||
|
||||
# Function to build the project
|
||||
build_project() {
|
||||
# package_path parameter is kept for backward compatibility
|
||||
local current_dir
|
||||
current_dir=$(pwd) || return
|
||||
|
||||
if ! cd "${DEFAULT_PACKAGE_PATH}"; then
|
||||
echo "Failed to change directory to ${DEFAULT_PACKAGE_PATH}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
LLGO_DEBUG_SYMBOLS=1 llgo build -o "debug.out" . || {
|
||||
local ret=$?
|
||||
cd "$current_dir" || return
|
||||
return $ret
|
||||
}
|
||||
|
||||
cd "$current_dir" || return
|
||||
}
|
||||
3
_lldb/lldbtest/go.mod
Normal file
3
_lldb/lldbtest/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module lldbtest
|
||||
|
||||
go 1.20
|
||||
569
_lldb/lldbtest/main.go
Normal file
569
_lldb/lldbtest/main.go
Normal file
@@ -0,0 +1,569 @@
|
||||
package main
|
||||
|
||||
import "errors"
|
||||
|
||||
type Base struct {
|
||||
name string
|
||||
}
|
||||
|
||||
type E struct {
|
||||
// Base
|
||||
i int
|
||||
}
|
||||
type StructWithAllTypeFields struct {
|
||||
i8 int8
|
||||
i16 int16
|
||||
i32 int32
|
||||
i64 int64
|
||||
i int
|
||||
u8 uint8
|
||||
u16 uint16
|
||||
u32 uint32
|
||||
u64 uint64
|
||||
u uint
|
||||
f32 float32
|
||||
f64 float64
|
||||
b bool
|
||||
c64 complex64
|
||||
c128 complex128
|
||||
slice []int
|
||||
arr [3]int
|
||||
arr2 [3]E
|
||||
s string
|
||||
e E
|
||||
pf *StructWithAllTypeFields // resursive
|
||||
pi *int
|
||||
intr Interface
|
||||
m map[string]uint64
|
||||
c chan int
|
||||
err error
|
||||
fn func(string) (int, error)
|
||||
pad1 int
|
||||
pad2 int
|
||||
}
|
||||
|
||||
type Interface interface {
|
||||
Foo(a []int, b string) int
|
||||
}
|
||||
|
||||
type Struct struct{}
|
||||
|
||||
func (s *Struct) Foo(a []int, b string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func FuncWithAllTypeStructParam(s StructWithAllTypeFields) {
|
||||
println(&s)
|
||||
// Expected:
|
||||
// all variables: s
|
||||
// s.i8: '\x01'
|
||||
// s.i16: 2
|
||||
// s.i32: 3
|
||||
// s.i64: 4
|
||||
// s.i: 5
|
||||
// s.u8: '\x06'
|
||||
// s.u16: 7
|
||||
// s.u32: 8
|
||||
// s.u64: 9
|
||||
// s.u: 10
|
||||
// s.f32: 11
|
||||
// s.f64: 12
|
||||
// s.b: true
|
||||
// s.c64: complex64{real = 13, imag = 14}
|
||||
// s.c128: complex128{real = 15, imag = 16}
|
||||
// s.slice: []int{21, 22, 23}
|
||||
// s.arr: [3]int{24, 25, 26}
|
||||
// s.arr2: [3]lldbtest.E{{i = 27}, {i = 28}, {i = 29}}
|
||||
// s.s: "hello"
|
||||
// s.e: lldbtest.E{i = 30}
|
||||
// s.pad1: 100
|
||||
// s.pad2: 200
|
||||
s.i8 = '\b'
|
||||
// Expected:
|
||||
// s.i8: '\b'
|
||||
// s.i16: 2
|
||||
println(len(s.s), s.i8)
|
||||
}
|
||||
|
||||
// Params is a function with all types of parameters.
|
||||
func FuncWithAllTypeParams(
|
||||
i8 int8,
|
||||
i16 int16,
|
||||
i32 int32,
|
||||
i64 int64,
|
||||
i int,
|
||||
u8 uint8,
|
||||
u16 uint16,
|
||||
u32 uint32,
|
||||
u64 uint64,
|
||||
u uint,
|
||||
f32 float32,
|
||||
f64 float64,
|
||||
b bool,
|
||||
c64 complex64,
|
||||
c128 complex128,
|
||||
slice []int,
|
||||
arr [3]int,
|
||||
arr2 [3]E,
|
||||
s string,
|
||||
e E,
|
||||
f StructWithAllTypeFields,
|
||||
pf *StructWithAllTypeFields,
|
||||
pi *int,
|
||||
intr Interface,
|
||||
m map[string]uint64,
|
||||
c chan int,
|
||||
err error,
|
||||
fn func(string) (int, error),
|
||||
) (int, error) {
|
||||
// Expected:
|
||||
// all variables: i8 i16 i32 i64 i u8 u16 u32 u64 u f32 f64 b c64 c128 slice arr arr2 s e f pf pi intr m c err fn
|
||||
// i32: 3
|
||||
// i64: 4
|
||||
// i: 5
|
||||
// u32: 8
|
||||
// u64: 9
|
||||
// u: 10
|
||||
// f32: 11
|
||||
// f64: 12
|
||||
// slice: []int{21, 22, 23}
|
||||
// arr: [3]int{24, 25, 26}
|
||||
// arr2: [3]lldbtest.E{{i = 27}, {i = 28}, {i = 29}}
|
||||
// slice[0]: 21
|
||||
// slice[1]: 22
|
||||
// slice[2]: 23
|
||||
// arr[0]: 24
|
||||
// arr[1]: 25
|
||||
// arr[2]: 26
|
||||
// arr2[0].i: 27
|
||||
// arr2[1].i: 28
|
||||
// arr2[2].i: 29
|
||||
// e: lldbtest.E{i = 30}
|
||||
|
||||
// Expected(skip):
|
||||
// i8: '\b'
|
||||
// i16: 2
|
||||
// u8: '\x06'
|
||||
// u16: 7
|
||||
// b: true
|
||||
println(
|
||||
i8, i16, i32, i64, i, u8, u16, u32, u64, u,
|
||||
f32, f64, b,
|
||||
c64, c128,
|
||||
slice, arr[0:],
|
||||
s,
|
||||
&e,
|
||||
&f, pf, pi, intr, m,
|
||||
c,
|
||||
err,
|
||||
fn,
|
||||
)
|
||||
i8 = 9
|
||||
i16 = 10
|
||||
i32 = 11
|
||||
i64 = 12
|
||||
i = 13
|
||||
u8 = 14
|
||||
u16 = 15
|
||||
u32 = 16
|
||||
u64 = 17
|
||||
u = 18
|
||||
f32 = 19
|
||||
f64 = 20
|
||||
b = false
|
||||
c64 = 21 + 22i
|
||||
c128 = 23 + 24i
|
||||
slice = []int{31, 32, 33}
|
||||
arr = [3]int{34, 35, 36}
|
||||
arr2 = [3]E{{i: 37}, {i: 38}, {i: 39}}
|
||||
s = "world"
|
||||
e = E{i: 40}
|
||||
|
||||
println(i8, i16, i32, i64, i, u8, u16, u32, u64, u,
|
||||
f32, f64, b,
|
||||
c64, c128,
|
||||
slice, arr[0:], &arr2,
|
||||
s,
|
||||
&e,
|
||||
&f, pf, pi, intr, m,
|
||||
c,
|
||||
err,
|
||||
fn,
|
||||
)
|
||||
// Expected:
|
||||
// i8: '\t'
|
||||
// i16: 10
|
||||
// i32: 11
|
||||
// i64: 12
|
||||
// i: 13
|
||||
// u8: '\x0e'
|
||||
// u16: 15
|
||||
// u32: 16
|
||||
// u64: 17
|
||||
// u: 18
|
||||
// f32: 19
|
||||
// f64: 20
|
||||
// b: false
|
||||
// c64: complex64{real = 21, imag = 22}
|
||||
// c128: complex128{real = 23, imag = 24}
|
||||
// slice: []int{31, 32, 33}
|
||||
// arr2: [3]lldbtest.E{{i = 37}, {i = 38}, {i = 39}}
|
||||
// s: "world"
|
||||
// e: lldbtest.E{i = 40}
|
||||
|
||||
// Expected(skip):
|
||||
// arr: [3]int{34, 35, 36}
|
||||
return 1, errors.New("some error")
|
||||
}
|
||||
|
||||
type TinyStruct struct {
|
||||
I int
|
||||
}
|
||||
|
||||
type SmallStruct struct {
|
||||
I int
|
||||
J int
|
||||
}
|
||||
|
||||
type MidStruct struct {
|
||||
I int
|
||||
J int
|
||||
K int
|
||||
}
|
||||
|
||||
type BigStruct struct {
|
||||
I int
|
||||
J int
|
||||
K int
|
||||
L int
|
||||
M int
|
||||
N int
|
||||
O int
|
||||
P int
|
||||
Q int
|
||||
R int
|
||||
}
|
||||
|
||||
func FuncStructParams(t TinyStruct, s SmallStruct, m MidStruct, b BigStruct) {
|
||||
// println(&t, &s, &m, &b)
|
||||
// Expected:
|
||||
// all variables: t s m b
|
||||
// t.I: 1
|
||||
// s.I: 2
|
||||
// s.J: 3
|
||||
// m.I: 4
|
||||
// m.J: 5
|
||||
// m.K: 6
|
||||
// b.I: 7
|
||||
// b.J: 8
|
||||
// b.K: 9
|
||||
// b.L: 10
|
||||
// b.M: 11
|
||||
// b.N: 12
|
||||
// b.O: 13
|
||||
// b.P: 14
|
||||
// b.Q: 15
|
||||
// b.R: 16
|
||||
println(t.I, s.I, s.J, m.I, m.J, m.K, b.I, b.J, b.K, b.L, b.M, b.N, b.O, b.P, b.Q, b.R)
|
||||
t.I = 10
|
||||
s.I = 20
|
||||
s.J = 21
|
||||
m.I = 40
|
||||
m.J = 41
|
||||
m.K = 42
|
||||
b.I = 70
|
||||
b.J = 71
|
||||
b.K = 72
|
||||
b.L = 73
|
||||
b.M = 74
|
||||
b.N = 75
|
||||
b.O = 76
|
||||
b.P = 77
|
||||
b.Q = 78
|
||||
b.R = 79
|
||||
// Expected:
|
||||
// all variables: t s m b
|
||||
// t.I: 10
|
||||
// s.I: 20
|
||||
// s.J: 21
|
||||
// m.I: 40
|
||||
// m.J: 41
|
||||
// m.K: 42
|
||||
// b.I: 70
|
||||
// b.J: 71
|
||||
// b.K: 72
|
||||
// b.L: 73
|
||||
// b.M: 74
|
||||
// b.N: 75
|
||||
// b.O: 76
|
||||
// b.P: 77
|
||||
// b.Q: 78
|
||||
// b.R: 79
|
||||
println("done")
|
||||
}
|
||||
|
||||
func FuncStructPtrParams(t *TinyStruct, s *SmallStruct, m *MidStruct, b *BigStruct) {
|
||||
// Expected:
|
||||
// all variables: t s m b
|
||||
// t.I: 1
|
||||
// s.I: 2
|
||||
// s.J: 3
|
||||
// m.I: 4
|
||||
// m.J: 5
|
||||
// m.K: 6
|
||||
// b.I: 7
|
||||
// b.J: 8
|
||||
// b.K: 9
|
||||
// b.L: 10
|
||||
// b.M: 11
|
||||
// b.N: 12
|
||||
// b.O: 13
|
||||
// b.P: 14
|
||||
// b.Q: 15
|
||||
// b.R: 16
|
||||
println(t, s, m, b)
|
||||
t.I = 10
|
||||
s.I = 20
|
||||
s.J = 21
|
||||
m.I = 40
|
||||
m.J = 41
|
||||
m.K = 42
|
||||
b.I = 70
|
||||
b.J = 71
|
||||
b.K = 72
|
||||
b.L = 73
|
||||
b.M = 74
|
||||
b.N = 75
|
||||
b.O = 76
|
||||
b.P = 77
|
||||
b.Q = 78
|
||||
b.R = 79
|
||||
// Expected:
|
||||
// all variables: t s m b
|
||||
// t.I: 10
|
||||
// s.I: 20
|
||||
// s.J: 21
|
||||
// m.I: 40
|
||||
// m.J: 41
|
||||
// m.K: 42
|
||||
// b.I: 70
|
||||
// b.J: 71
|
||||
// b.K: 72
|
||||
// b.L: 73
|
||||
// b.M: 74
|
||||
// b.N: 75
|
||||
// b.O: 76
|
||||
// b.P: 77
|
||||
// b.Q: 78
|
||||
// b.R: 79
|
||||
println(t.I, s.I, s.J, m.I, m.J, m.K, b.I, b.J, b.K, b.L, b.M, b.N, b.O, b.P, b.Q, b.R)
|
||||
println("done")
|
||||
}
|
||||
|
||||
func ScopeIf(branch int) {
|
||||
a := 1
|
||||
// Expected:
|
||||
// all variables: a branch
|
||||
// a: 1
|
||||
if branch == 1 {
|
||||
b := 2
|
||||
c := 3
|
||||
// Expected:
|
||||
// all variables: a b c branch
|
||||
// a: 1
|
||||
// b: 2
|
||||
// c: 3
|
||||
// branch: 1
|
||||
println(a, b, c)
|
||||
} else {
|
||||
c := 3
|
||||
d := 4
|
||||
// Expected:
|
||||
// all variables: a c d branch
|
||||
// a: 1
|
||||
// c: 3
|
||||
// d: 4
|
||||
// branch: 0
|
||||
println(a, c, d)
|
||||
}
|
||||
// Expected:
|
||||
// all variables: a branch
|
||||
// a: 1
|
||||
println("a:", a)
|
||||
}
|
||||
|
||||
func ScopeFor() {
|
||||
a := 1
|
||||
for i := 0; i < 10; i++ {
|
||||
switch i {
|
||||
case 0:
|
||||
println("i is 0")
|
||||
// Expected:
|
||||
// all variables: i a
|
||||
// i: 0
|
||||
// a: 1
|
||||
println("i:", i)
|
||||
case 1:
|
||||
println("i is 1")
|
||||
// Expected:
|
||||
// all variables: i a
|
||||
// i: 1
|
||||
// a: 1
|
||||
println("i:", i)
|
||||
default:
|
||||
println("i is", i)
|
||||
}
|
||||
}
|
||||
println("a:", a)
|
||||
}
|
||||
|
||||
func ScopeSwitch(i int) {
|
||||
a := 0
|
||||
switch i {
|
||||
case 1:
|
||||
b := 1
|
||||
println("i is 1")
|
||||
// Expected:
|
||||
// all variables: i a b
|
||||
// i: 1
|
||||
// a: 0
|
||||
// b: 1
|
||||
println("i:", i, "a:", a, "b:", b)
|
||||
case 2:
|
||||
c := 2
|
||||
println("i is 2")
|
||||
// Expected:
|
||||
// all variables: i a c
|
||||
// i: 2
|
||||
// a: 0
|
||||
// c: 2
|
||||
println("i:", i, "a:", a, "c:", c)
|
||||
default:
|
||||
d := 3
|
||||
println("i is", i)
|
||||
// Expected:
|
||||
// all variables: i a d
|
||||
// i: 3
|
||||
// a: 0
|
||||
// d: 3
|
||||
println("i:", i, "a:", a, "d:", d)
|
||||
}
|
||||
// Expected:
|
||||
// all variables: a i
|
||||
// a: 0
|
||||
println("a:", a)
|
||||
}
|
||||
|
||||
func main() {
|
||||
FuncStructParams(TinyStruct{I: 1}, SmallStruct{I: 2, J: 3}, MidStruct{I: 4, J: 5, K: 6}, BigStruct{I: 7, J: 8, K: 9, L: 10, M: 11, N: 12, O: 13, P: 14, Q: 15, R: 16})
|
||||
FuncStructPtrParams(&TinyStruct{I: 1}, &SmallStruct{I: 2, J: 3}, &MidStruct{I: 4, J: 5, K: 6}, &BigStruct{I: 7, J: 8, K: 9, L: 10, M: 11, N: 12, O: 13, P: 14, Q: 15, R: 16})
|
||||
i := 100
|
||||
s := StructWithAllTypeFields{
|
||||
i8: 1,
|
||||
i16: 2,
|
||||
i32: 3,
|
||||
i64: 4,
|
||||
i: 5,
|
||||
u8: 6,
|
||||
u16: 7,
|
||||
u32: 8,
|
||||
u64: 9,
|
||||
u: 10,
|
||||
f32: 11,
|
||||
f64: 12,
|
||||
b: true,
|
||||
c64: 13 + 14i,
|
||||
c128: 15 + 16i,
|
||||
slice: []int{21, 22, 23},
|
||||
arr: [3]int{24, 25, 26},
|
||||
arr2: [3]E{{i: 27}, {i: 28}, {i: 29}},
|
||||
s: "hello",
|
||||
e: E{i: 30},
|
||||
pf: &StructWithAllTypeFields{i16: 100},
|
||||
pi: &i,
|
||||
intr: &Struct{},
|
||||
m: map[string]uint64{"a": 31, "b": 32},
|
||||
c: make(chan int),
|
||||
err: errors.New("Test error"),
|
||||
fn: func(s string) (int, error) {
|
||||
println("fn:", s)
|
||||
i = 201
|
||||
return 1, errors.New("fn error")
|
||||
},
|
||||
pad1: 100,
|
||||
pad2: 200,
|
||||
}
|
||||
// Expected:
|
||||
// all variables: s i err
|
||||
// s.i8: '\x01'
|
||||
// s.i16: 2
|
||||
// s.i32: 3
|
||||
// s.i64: 4
|
||||
// s.i: 5
|
||||
// s.u8: '\x06'
|
||||
// s.u16: 7
|
||||
// s.u32: 8
|
||||
// s.u64: 9
|
||||
// s.u: 10
|
||||
// s.f32: 11
|
||||
// s.f64: 12
|
||||
// s.b: true
|
||||
// s.c64: complex64{real = 13, imag = 14}
|
||||
// s.c128: complex128{real = 15, imag = 16}
|
||||
// s.slice: []int{21, 22, 23}
|
||||
// s.arr: [3]int{24, 25, 26}
|
||||
// s.arr2: [3]lldbtest.E{{i = 27}, {i = 28}, {i = 29}}
|
||||
// s.s: "hello"
|
||||
// s.e: lldbtest.E{i = 30}
|
||||
// s.pf.i16: 100
|
||||
// *(s.pf).i16: 100
|
||||
// *(s.pi): 100
|
||||
globalStructPtr = &s
|
||||
globalStruct = s
|
||||
println("globalInt:", globalInt)
|
||||
// Expected(skip):
|
||||
// all variables: globalInt globalStruct globalStructPtr s i err
|
||||
println("s:", &s)
|
||||
FuncWithAllTypeStructParam(s)
|
||||
println("called function with struct")
|
||||
i, err := FuncWithAllTypeParams(
|
||||
s.i8, s.i16, s.i32, s.i64, s.i, s.u8, s.u16, s.u32, s.u64, s.u,
|
||||
s.f32, s.f64, s.b,
|
||||
s.c64, s.c128,
|
||||
s.slice, s.arr, s.arr2,
|
||||
s.s,
|
||||
s.e, s,
|
||||
s.pf, s.pi,
|
||||
s.intr,
|
||||
s.m,
|
||||
s.c,
|
||||
s.err,
|
||||
s.fn,
|
||||
)
|
||||
println(i, err)
|
||||
ScopeIf(1)
|
||||
ScopeIf(0)
|
||||
ScopeFor()
|
||||
ScopeSwitch(1)
|
||||
ScopeSwitch(2)
|
||||
ScopeSwitch(3)
|
||||
println(globalStructPtr)
|
||||
println(&globalStruct)
|
||||
s.i8 = 0x12
|
||||
println(s.i8)
|
||||
// Expected:
|
||||
// all variables: s i err
|
||||
// s.i8: '\x12'
|
||||
|
||||
// Expected(skip):
|
||||
// globalStruct.i8: '\x01'
|
||||
println((*globalStructPtr).i8)
|
||||
println("done")
|
||||
println("")
|
||||
println(&s, &globalStruct, globalStructPtr.i16, globalStructPtr)
|
||||
globalStructPtr = nil
|
||||
}
|
||||
|
||||
var globalInt int = 301
|
||||
var globalStruct StructWithAllTypeFields
|
||||
var globalStructPtr *StructWithAllTypeFields
|
||||
297
_lldb/llgo_plugin.py
Normal file
297
_lldb/llgo_plugin.py
Normal file
@@ -0,0 +1,297 @@
|
||||
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
|
||||
|
||||
from typing import List, Optional, Dict, Any, Tuple
|
||||
import re
|
||||
import lldb
|
||||
|
||||
|
||||
def log(*args: Any, **kwargs: Any) -> None:
|
||||
print(*args, **kwargs, flush=True)
|
||||
|
||||
|
||||
def __lldb_init_module(debugger: lldb.SBDebugger, _: Dict[str, Any]) -> None:
|
||||
debugger.HandleCommand(
|
||||
'command script add -f llgo_plugin.print_go_expression p')
|
||||
debugger.HandleCommand(
|
||||
'command script add -f llgo_plugin.print_all_variables v')
|
||||
|
||||
|
||||
def is_llgo_compiler(_target: lldb.SBTarget) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def get_indexed_value(value: lldb.SBValue, index: int) -> Optional[lldb.SBValue]:
|
||||
if not value or not value.IsValid():
|
||||
return None
|
||||
|
||||
type_name = value.GetType().GetName()
|
||||
|
||||
if type_name.startswith('[]'): # Slice
|
||||
data_ptr = value.GetChildMemberWithName('data')
|
||||
element_type = data_ptr.GetType().GetPointeeType()
|
||||
element_size = element_type.GetByteSize()
|
||||
ptr_value = int(data_ptr.GetValue(), 16)
|
||||
element_address = ptr_value + index * element_size
|
||||
target = value.GetTarget()
|
||||
return target.CreateValueFromAddress(
|
||||
f"element_{index}", lldb.SBAddress(element_address, target), element_type)
|
||||
elif value.GetType().IsArrayType(): # Array
|
||||
return value.GetChildAtIndex(index)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def evaluate_expression(frame: lldb.SBFrame, expression: str) -> Optional[lldb.SBValue]:
|
||||
parts = re.findall(r'\*|\w+|\(|\)|\[.*?\]|\.', expression)
|
||||
|
||||
def evaluate_part(i: int) -> Tuple[Optional[lldb.SBValue], int]:
|
||||
nonlocal parts
|
||||
value: Optional[lldb.SBValue] = None
|
||||
while i < len(parts):
|
||||
part = parts[i]
|
||||
|
||||
if part == '*':
|
||||
sub_value, i = evaluate_part(i + 1)
|
||||
if sub_value and sub_value.IsValid():
|
||||
value = sub_value.Dereference()
|
||||
else:
|
||||
return None, i
|
||||
elif part == '(':
|
||||
depth = 1
|
||||
j = i + 1
|
||||
while j < len(parts) and depth > 0:
|
||||
if parts[j] == '(':
|
||||
depth += 1
|
||||
elif parts[j] == ')':
|
||||
depth -= 1
|
||||
j += 1
|
||||
value, i = evaluate_part(i + 1)
|
||||
i = j - 1
|
||||
elif part == ')':
|
||||
return value, i + 1
|
||||
elif part == '.':
|
||||
if value is None:
|
||||
value = frame.FindVariable(parts[i+1])
|
||||
else:
|
||||
value = value.GetChildMemberWithName(parts[i+1])
|
||||
i += 2
|
||||
elif part.startswith('['):
|
||||
index = int(part[1:-1])
|
||||
value = get_indexed_value(value, index)
|
||||
i += 1
|
||||
else:
|
||||
if value is None:
|
||||
value = frame.FindVariable(part)
|
||||
else:
|
||||
value = value.GetChildMemberWithName(part)
|
||||
i += 1
|
||||
|
||||
if not value or not value.IsValid():
|
||||
return None, i
|
||||
|
||||
return value, i
|
||||
|
||||
value, _ = evaluate_part(0)
|
||||
return value
|
||||
|
||||
|
||||
def print_go_expression(debugger: lldb.SBDebugger, command: str, result: lldb.SBCommandReturnObject, _internal_dict: Dict[str, Any]) -> None:
|
||||
frame = debugger.GetSelectedTarget().GetProcess(
|
||||
).GetSelectedThread().GetSelectedFrame()
|
||||
value = evaluate_expression(frame, command)
|
||||
if value and value.IsValid():
|
||||
result.AppendMessage(format_value(value, debugger))
|
||||
else:
|
||||
result.AppendMessage(
|
||||
f"Error: Unable to evaluate expression '{command}'")
|
||||
|
||||
|
||||
def print_all_variables(debugger: lldb.SBDebugger, _command: str, result: lldb.SBCommandReturnObject, _internal_dict: Dict[str, Any]) -> None:
|
||||
target = debugger.GetSelectedTarget()
|
||||
if not is_llgo_compiler(target):
|
||||
result.AppendMessage("Not a LLGo compiled binary.")
|
||||
return
|
||||
|
||||
frame = debugger.GetSelectedTarget().GetProcess(
|
||||
).GetSelectedThread().GetSelectedFrame()
|
||||
variables = frame.GetVariables(True, True, True, True)
|
||||
|
||||
output: List[str] = []
|
||||
for var in variables:
|
||||
type_name = map_type_name(var.GetType().GetName())
|
||||
formatted = format_value(var, debugger, include_type=False, indent=0)
|
||||
output.append(f"var {var.GetName()} {type_name} = {formatted}")
|
||||
|
||||
result.AppendMessage("\n".join(output))
|
||||
|
||||
|
||||
def is_pointer(frame: lldb.SBFrame, var_name: str) -> bool:
|
||||
var = frame.FindVariable(var_name)
|
||||
return var.IsValid() and var.GetType().IsPointerType()
|
||||
|
||||
|
||||
def format_value(var: lldb.SBValue, debugger: lldb.SBDebugger, include_type: bool = True, indent: int = 0) -> str:
|
||||
if not var.IsValid():
|
||||
return "<variable not available>"
|
||||
|
||||
var_type = var.GetType()
|
||||
type_class = var_type.GetTypeClass()
|
||||
type_name = map_type_name(var_type.GetName())
|
||||
|
||||
# Handle typedef types
|
||||
original_type_name = type_name
|
||||
while var_type.IsTypedefType():
|
||||
var_type = var_type.GetTypedefedType()
|
||||
type_name = map_type_name(var_type.GetName())
|
||||
type_class = var_type.GetTypeClass()
|
||||
|
||||
if var_type.IsPointerType():
|
||||
return format_pointer(var, debugger, indent, original_type_name)
|
||||
|
||||
if type_name.startswith('[]'): # Slice
|
||||
return format_slice(var, debugger, indent)
|
||||
elif var_type.IsArrayType():
|
||||
return format_array(var, debugger, indent)
|
||||
elif type_name == 'string': # String
|
||||
return format_string(var)
|
||||
elif type_class in [lldb.eTypeClassStruct, lldb.eTypeClassClass]:
|
||||
return format_struct(var, debugger, include_type, indent, original_type_name)
|
||||
else:
|
||||
value = var.GetValue()
|
||||
summary = var.GetSummary()
|
||||
if value is not None:
|
||||
return f"{value}" if include_type else str(value)
|
||||
elif summary is not None:
|
||||
return f"{summary}" if include_type else summary
|
||||
else:
|
||||
return "<variable not available>"
|
||||
|
||||
|
||||
def format_slice(var: lldb.SBValue, debugger: lldb.SBDebugger, indent: int) -> str:
|
||||
length = var.GetChildMemberWithName('len').GetValue()
|
||||
if length is None:
|
||||
return "<variable not available>"
|
||||
length = int(length)
|
||||
data_ptr = var.GetChildMemberWithName('data')
|
||||
elements: List[str] = []
|
||||
|
||||
ptr_value = int(data_ptr.GetValue(), 16)
|
||||
element_type = data_ptr.GetType().GetPointeeType()
|
||||
element_size = element_type.GetByteSize()
|
||||
|
||||
target = debugger.GetSelectedTarget()
|
||||
indent_str = ' ' * indent
|
||||
next_indent_str = ' ' * (indent + 1)
|
||||
|
||||
for i in range(length):
|
||||
element_address = ptr_value + i * element_size
|
||||
element = target.CreateValueFromAddress(
|
||||
f"element_{i}", lldb.SBAddress(element_address, target), element_type)
|
||||
value = format_value(
|
||||
element, debugger, include_type=False, indent=indent+1)
|
||||
elements.append(value)
|
||||
|
||||
type_name = var.GetType().GetName()
|
||||
|
||||
if len(elements) > 5: # 如果元素数量大于5,则进行折行显示
|
||||
result = f"{type_name}{{\n{next_indent_str}" + \
|
||||
f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}"
|
||||
else:
|
||||
result = f"{type_name}{{{', '.join(elements)}}}"
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def format_array(var: lldb.SBValue, debugger: lldb.SBDebugger, indent: int) -> str:
|
||||
elements: List[str] = []
|
||||
indent_str = ' ' * indent
|
||||
next_indent_str = ' ' * (indent + 1)
|
||||
|
||||
for i in range(var.GetNumChildren()):
|
||||
value = format_value(var.GetChildAtIndex(
|
||||
i), debugger, include_type=False, indent=indent+1)
|
||||
elements.append(value)
|
||||
|
||||
array_size = var.GetNumChildren()
|
||||
element_type = map_type_name(var.GetType().GetArrayElementType().GetName())
|
||||
type_name = f"[{array_size}]{element_type}"
|
||||
|
||||
if len(elements) > 5: # wrap line if too many elements
|
||||
return f"{type_name}{{\n{next_indent_str}" + f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}"
|
||||
else:
|
||||
return f"{type_name}{{{', '.join(elements)}}}"
|
||||
|
||||
|
||||
def format_string(var: lldb.SBValue) -> str:
|
||||
summary = var.GetSummary()
|
||||
if summary is not None:
|
||||
return summary # Keep the quotes
|
||||
else:
|
||||
data = var.GetChildMemberWithName('data').GetValue()
|
||||
length = var.GetChildMemberWithName('len').GetValue()
|
||||
if data and length:
|
||||
length = int(length)
|
||||
error = lldb.SBError()
|
||||
return '"%s"' % var.process.ReadCStringFromMemory(int(data, 16), length + 1, error)
|
||||
return "<variable not available>"
|
||||
|
||||
|
||||
def format_struct(var: lldb.SBValue, debugger: lldb.SBDebugger, include_type: bool = True, indent: int = 0, type_name: str = "") -> str:
|
||||
children: List[str] = []
|
||||
indent_str = ' ' * indent
|
||||
next_indent_str = ' ' * (indent + 1)
|
||||
|
||||
for i in range(var.GetNumChildren()):
|
||||
child = var.GetChildAtIndex(i)
|
||||
child_name = child.GetName()
|
||||
child_value = format_value(
|
||||
child, debugger, include_type=False, indent=indent+1)
|
||||
children.append(f"{child_name} = {child_value}")
|
||||
|
||||
if len(children) > 5: # 如果字段数量大于5,则进行折行显示
|
||||
struct_content = "{\n" + ",\n".join(
|
||||
[f"{next_indent_str}{child}" for child in children]) + f"\n{indent_str}}}"
|
||||
else:
|
||||
struct_content = f"{{{', '.join(children)}}}"
|
||||
|
||||
if include_type:
|
||||
return f"{type_name}{struct_content}"
|
||||
else:
|
||||
return struct_content
|
||||
|
||||
|
||||
def format_pointer(var: lldb.SBValue, _debugger: lldb.SBDebugger, _indent: int, _type_name: str) -> str:
|
||||
if not var.IsValid() or var.GetValueAsUnsigned() == 0:
|
||||
return "<variable not available>"
|
||||
return var.GetValue() # Return the address as a string
|
||||
|
||||
|
||||
def map_type_name(type_name: str) -> str:
|
||||
# Handle pointer types
|
||||
if type_name.endswith('*'):
|
||||
base_type = type_name[:-1].strip()
|
||||
mapped_base_type = map_type_name(base_type)
|
||||
return f"*{mapped_base_type}"
|
||||
|
||||
# Map other types
|
||||
type_mapping: Dict[str, str] = {
|
||||
'long': 'int',
|
||||
'void': 'unsafe.Pointer',
|
||||
'char': 'byte',
|
||||
'short': 'int16',
|
||||
'int': 'int32',
|
||||
'long long': 'int64',
|
||||
'unsigned char': 'uint8',
|
||||
'unsigned short': 'uint16',
|
||||
'unsigned int': 'uint32',
|
||||
'unsigned long': 'uint',
|
||||
'unsigned long long': 'uint64',
|
||||
'float': 'float32',
|
||||
'double': 'float64',
|
||||
}
|
||||
|
||||
for c_type, go_type in type_mapping.items():
|
||||
if type_name.startswith(c_type):
|
||||
return type_name.replace(c_type, go_type, 1)
|
||||
|
||||
return type_name
|
||||
15
_lldb/runlldb.sh
Executable file
15
_lldb/runlldb.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Source common functions and variables
|
||||
# shellcheck source=./_lldb/common.sh
|
||||
source "$(dirname "$0")/common.sh"
|
||||
|
||||
executable="$1"
|
||||
|
||||
# Get the directory of the current script
|
||||
script_dir="$(dirname "$0")"
|
||||
|
||||
# Run LLDB with the LLGO plugin
|
||||
"$LLDB_PATH" -O "command script import ${script_dir}/llgo_plugin.py" "$executable"
|
||||
69
_lldb/runtest.sh
Executable file
69
_lldb/runtest.sh
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Source common functions and variables
|
||||
# shellcheck source=./_lldb/common.sh
|
||||
# shellcheck disable=SC1091
|
||||
source "$(dirname "$0")/common.sh" || exit 1
|
||||
|
||||
# Parse command-line arguments
|
||||
package_path="$DEFAULT_PACKAGE_PATH"
|
||||
verbose=False
|
||||
interactive=False
|
||||
plugin_path=None
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-v|--verbose)
|
||||
verbose=True
|
||||
shift
|
||||
;;
|
||||
-i|--interactive)
|
||||
interactive=True
|
||||
shift
|
||||
;;
|
||||
-p|--plugin)
|
||||
plugin_path="\"$2\""
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
package_path="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Build the project
|
||||
build_project "$package_path" || exit 1
|
||||
|
||||
# Set up the result file path
|
||||
result_file="/tmp/lldb_exit_code"
|
||||
|
||||
# Prepare LLDB commands
|
||||
lldb_commands=(
|
||||
"command script import ../llgo_plugin.py"
|
||||
"command script import ../test.py"
|
||||
"script test.run_tests_with_result('./debug.out', ['main.go'], $verbose, $interactive, $plugin_path, '$result_file')"
|
||||
"quit"
|
||||
)
|
||||
|
||||
# Run LLDB with prepared commands
|
||||
lldb_command_string=""
|
||||
for cmd in "${lldb_commands[@]}"; do
|
||||
lldb_command_string+=" -o \"$cmd\""
|
||||
done
|
||||
|
||||
cd "$package_path"
|
||||
# Run LLDB with the test script
|
||||
eval "$LLDB_PATH $lldb_command_string"
|
||||
|
||||
# Read the exit code from the result file
|
||||
if [ -f "$result_file" ]; then
|
||||
exit_code=$(cat "$result_file")
|
||||
rm "$result_file"
|
||||
exit "$exit_code"
|
||||
else
|
||||
echo "Error: Could not find exit code file"
|
||||
exit 1
|
||||
fi
|
||||
402
_lldb/test.py
Normal file
402
_lldb/test.py
Normal file
@@ -0,0 +1,402 @@
|
||||
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import signal
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional, Set, Dict, Any
|
||||
import lldb
|
||||
import llgo_plugin
|
||||
from llgo_plugin import log
|
||||
|
||||
|
||||
class LLDBTestException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Test:
|
||||
source_file: str
|
||||
line_number: int
|
||||
variable: str
|
||||
expected_value: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestResult:
|
||||
test: Test
|
||||
status: str
|
||||
actual: Optional[str] = None
|
||||
message: Optional[str] = None
|
||||
missing: Optional[Set[str]] = None
|
||||
extra: Optional[Set[str]] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestCase:
|
||||
source_file: str
|
||||
start_line: int
|
||||
end_line: int
|
||||
tests: List[Test]
|
||||
|
||||
|
||||
@dataclass
|
||||
class CaseResult:
|
||||
test_case: TestCase
|
||||
function: str
|
||||
results: List[TestResult]
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestResults:
|
||||
total: int = 0
|
||||
passed: int = 0
|
||||
failed: int = 0
|
||||
case_results: List[CaseResult] = field(default_factory=list)
|
||||
|
||||
|
||||
class LLDBDebugger:
|
||||
def __init__(self, executable_path: str, plugin_path: Optional[str] = None) -> None:
|
||||
self.executable_path: str = executable_path
|
||||
self.plugin_path: Optional[str] = plugin_path
|
||||
self.debugger: lldb.SBDebugger = lldb.SBDebugger.Create()
|
||||
self.debugger.SetAsync(False)
|
||||
self.target: Optional[lldb.SBTarget] = None
|
||||
self.process: Optional[lldb.SBProcess] = None
|
||||
self.type_mapping: Dict[str, str] = {
|
||||
'long': 'int',
|
||||
'unsigned long': 'uint',
|
||||
}
|
||||
|
||||
def setup(self) -> None:
|
||||
if self.plugin_path:
|
||||
self.debugger.HandleCommand(
|
||||
f'command script import "{self.plugin_path}"')
|
||||
self.target = self.debugger.CreateTarget(self.executable_path)
|
||||
if not self.target:
|
||||
raise LLDBTestException(
|
||||
f"Failed to create target for {self.executable_path}")
|
||||
|
||||
self.debugger.HandleCommand(
|
||||
'command script add -f llgo_plugin.print_go_expression p')
|
||||
self.debugger.HandleCommand(
|
||||
'command script add -f llgo_plugin.print_all_variables v')
|
||||
|
||||
def set_breakpoint(self, file_spec: str, line_number: int) -> lldb.SBBreakpoint:
|
||||
bp = self.target.BreakpointCreateByLocation(file_spec, line_number)
|
||||
if not bp.IsValid():
|
||||
raise LLDBTestException(
|
||||
f"Failed to set breakpoint at {file_spec}: {line_number}")
|
||||
return bp
|
||||
|
||||
def run_to_breakpoint(self) -> None:
|
||||
if not self.process:
|
||||
self.process = self.target.LaunchSimple(None, None, os.getcwd())
|
||||
else:
|
||||
self.process.Continue()
|
||||
if self.process.GetState() != lldb.eStateStopped:
|
||||
raise LLDBTestException("Process didn't stop at breakpoint")
|
||||
|
||||
def get_variable_value(self, var_expression: str) -> Optional[str]:
|
||||
frame = self.process.GetSelectedThread().GetFrameAtIndex(0)
|
||||
value = llgo_plugin.evaluate_expression(frame, var_expression)
|
||||
if value and value.IsValid():
|
||||
return llgo_plugin.format_value(value, self.debugger)
|
||||
return None
|
||||
|
||||
def get_all_variable_names(self) -> Set[str]:
|
||||
frame = self.process.GetSelectedThread().GetFrameAtIndex(0)
|
||||
return set(var.GetName() for var in frame.GetVariables(True, True, True, True))
|
||||
|
||||
def get_current_function_name(self) -> str:
|
||||
frame = self.process.GetSelectedThread().GetFrameAtIndex(0)
|
||||
return frame.GetFunctionName()
|
||||
|
||||
def cleanup(self) -> None:
|
||||
if self.process and self.process.IsValid():
|
||||
self.process.Kill()
|
||||
lldb.SBDebugger.Destroy(self.debugger)
|
||||
|
||||
def run_console(self) -> bool:
|
||||
log("\nEntering LLDB interactive mode.")
|
||||
log("Type 'quit' to exit and continue with the next test case.")
|
||||
log("Use Ctrl+D to exit and continue, or Ctrl+C to abort all tests.")
|
||||
|
||||
old_stdin, old_stdout, old_stderr = sys.stdin, sys.stdout, sys.stderr
|
||||
sys.stdin, sys.stdout, sys.stderr = sys.__stdin__, sys.__stdout__, sys.__stderr__
|
||||
|
||||
self.debugger.SetAsync(True)
|
||||
self.debugger.HandleCommand("settings set auto-confirm true")
|
||||
self.debugger.HandleCommand("command script import lldb")
|
||||
|
||||
interpreter = self.debugger.GetCommandInterpreter()
|
||||
continue_tests = True
|
||||
|
||||
def keyboard_interrupt_handler(_sig: Any, _frame: Any) -> None:
|
||||
nonlocal continue_tests
|
||||
log("\nTest execution aborted by user.")
|
||||
continue_tests = False
|
||||
raise KeyboardInterrupt
|
||||
|
||||
original_handler = signal.signal(
|
||||
signal.SIGINT, keyboard_interrupt_handler)
|
||||
|
||||
try:
|
||||
while continue_tests:
|
||||
log("\n(lldb) ", end="")
|
||||
try:
|
||||
command = input().strip()
|
||||
except EOFError:
|
||||
log("\nExiting LLDB interactive mode. Continuing with next test case.")
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
if command.lower() == 'quit':
|
||||
log("\nExiting LLDB interactive mode. Continuing with next test case.")
|
||||
break
|
||||
|
||||
result = lldb.SBCommandReturnObject()
|
||||
interpreter.HandleCommand(command, result)
|
||||
log(result.GetOutput().rstrip() if result.Succeeded()
|
||||
else result.GetError().rstrip())
|
||||
|
||||
finally:
|
||||
signal.signal(signal.SIGINT, original_handler)
|
||||
sys.stdin, sys.stdout, sys.stderr = old_stdin, old_stdout, old_stderr
|
||||
|
||||
return continue_tests
|
||||
|
||||
|
||||
def parse_expected_values(source_files: List[str]) -> List[TestCase]:
|
||||
test_cases: List[TestCase] = []
|
||||
for source_file in source_files:
|
||||
with open(source_file, 'r', encoding='utf-8') as f:
|
||||
content = f.readlines()
|
||||
i = 0
|
||||
while i < len(content):
|
||||
line = content[i].strip()
|
||||
if line.startswith('// Expected:'):
|
||||
start_line = i + 1
|
||||
tests: List[Test] = []
|
||||
i += 1
|
||||
while i < len(content):
|
||||
line = content[i].strip()
|
||||
if not line.startswith('//'):
|
||||
break
|
||||
parts = line.lstrip('//').strip().split(':', 1)
|
||||
if len(parts) == 2:
|
||||
var, value = map(str.strip, parts)
|
||||
tests.append(Test(source_file, i + 1, var, value))
|
||||
i += 1
|
||||
end_line = i
|
||||
test_cases.append(
|
||||
TestCase(source_file, start_line, end_line, tests))
|
||||
else:
|
||||
i += 1
|
||||
return test_cases
|
||||
|
||||
|
||||
def execute_tests(executable_path: str, test_cases: List[TestCase], verbose: bool, interactive: bool, plugin_path: Optional[str]) -> TestResults:
|
||||
results = TestResults()
|
||||
|
||||
for test_case in test_cases:
|
||||
debugger = LLDBDebugger(executable_path, plugin_path)
|
||||
try:
|
||||
if verbose:
|
||||
log(
|
||||
f"\nSetting breakpoint at {test_case.source_file}:{test_case.end_line}")
|
||||
debugger.setup()
|
||||
debugger.set_breakpoint(test_case.source_file, test_case.end_line)
|
||||
debugger.run_to_breakpoint()
|
||||
|
||||
all_variable_names = debugger.get_all_variable_names()
|
||||
|
||||
case_result = execute_test_case(
|
||||
debugger, test_case, all_variable_names)
|
||||
|
||||
results.total += len(case_result.results)
|
||||
results.passed += sum(1 for r in case_result.results if r.status == 'pass')
|
||||
results.failed += sum(1 for r in case_result.results if r.status != 'pass')
|
||||
results.case_results.append(case_result)
|
||||
|
||||
case = case_result.test_case
|
||||
loc = f"{case.source_file}:{case.start_line}-{case.end_line}"
|
||||
if verbose or interactive or any(r.status != 'pass' for r in case_result.results):
|
||||
log(f"\nTest case: {loc} in function '{case_result.function}'")
|
||||
for result in case_result.results:
|
||||
print_test_result(result, verbose=verbose)
|
||||
|
||||
if interactive and any(r.status != 'pass' for r in case_result.results):
|
||||
log("\nTest case failed. Entering LLDB interactive mode.")
|
||||
continue_tests = debugger.run_console()
|
||||
if not continue_tests:
|
||||
log("Aborting all tests.")
|
||||
break
|
||||
|
||||
finally:
|
||||
debugger.cleanup()
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def run_tests(executable_path: str, source_files: List[str], verbose: bool, interactive: bool, plugin_path: Optional[str]) -> int:
|
||||
test_cases = parse_expected_values(source_files)
|
||||
if verbose:
|
||||
log(f"Running tests for {', '.join(source_files)} with {executable_path}")
|
||||
log(f"Found {len(test_cases)} test cases")
|
||||
|
||||
results = execute_tests(executable_path, test_cases,
|
||||
verbose, interactive, plugin_path)
|
||||
print_test_results(results)
|
||||
|
||||
# Return 0 if all tests passed, 1 otherwise
|
||||
return 0 if results.failed == 0 else 1
|
||||
|
||||
|
||||
def execute_test_case(debugger: LLDBDebugger, test_case: TestCase, all_variable_names: Set[str]) -> CaseResult:
|
||||
results: List[TestResult] = []
|
||||
|
||||
for test in test_case.tests:
|
||||
if test.variable == "all variables":
|
||||
result = execute_all_variables_test(test, all_variable_names)
|
||||
else:
|
||||
result = execute_single_variable_test(debugger, test)
|
||||
results.append(result)
|
||||
|
||||
return CaseResult(test_case, debugger.get_current_function_name(), results)
|
||||
|
||||
|
||||
def execute_all_variables_test(test: Test, all_variable_names: Set[str]) -> TestResult:
|
||||
expected_vars = set(test.expected_value.split())
|
||||
if expected_vars == all_variable_names:
|
||||
return TestResult(
|
||||
test=test,
|
||||
status='pass',
|
||||
actual=all_variable_names
|
||||
)
|
||||
else:
|
||||
return TestResult(
|
||||
test=test,
|
||||
status='fail',
|
||||
actual=all_variable_names,
|
||||
missing=expected_vars - all_variable_names,
|
||||
extra=all_variable_names - expected_vars
|
||||
)
|
||||
|
||||
|
||||
def execute_single_variable_test(debugger: LLDBDebugger, test: Test) -> TestResult:
|
||||
actual_value = debugger.get_variable_value(test.variable)
|
||||
if actual_value is None:
|
||||
return TestResult(
|
||||
test=test,
|
||||
status='error',
|
||||
message=f'Unable to fetch value for {test.variable}'
|
||||
)
|
||||
|
||||
actual_value = actual_value.strip()
|
||||
expected_value = test.expected_value.strip()
|
||||
|
||||
if actual_value == expected_value:
|
||||
return TestResult(
|
||||
test=test,
|
||||
status='pass',
|
||||
actual=actual_value
|
||||
)
|
||||
else:
|
||||
return TestResult(
|
||||
test=test,
|
||||
status='fail',
|
||||
actual=actual_value
|
||||
)
|
||||
|
||||
|
||||
def print_test_results(results: TestResults) -> None:
|
||||
log("\nTest results:")
|
||||
log(f" Total tests: {results.total}")
|
||||
log(f" Passed tests: {results.passed}")
|
||||
log(f" Failed tests: {results.failed}")
|
||||
if results.total == results.passed:
|
||||
log("All tests passed!")
|
||||
else:
|
||||
log("Some tests failed")
|
||||
|
||||
|
||||
def print_test_result(result: TestResult, verbose: bool) -> None:
|
||||
status_symbol = "✓" if result.status == 'pass' else "✗"
|
||||
status_text = "Pass" if result.status == 'pass' else "Fail"
|
||||
test = result.test
|
||||
|
||||
if result.status == 'pass':
|
||||
if verbose:
|
||||
log(f"{status_symbol} Line {test.line_number}, {test.variable}: {status_text}")
|
||||
if test.variable == 'all variables':
|
||||
log(f" Variables: {', '.join(sorted(result.actual))}")
|
||||
else: # fail or error
|
||||
log(f"{status_symbol} Line {test.line_number}, {test.variable}: {status_text}")
|
||||
if test.variable == 'all variables':
|
||||
if result.missing:
|
||||
log(f" Missing variables: {', '.join(sorted(result.missing))}")
|
||||
if result.extra:
|
||||
log(f" Extra variables: {', '.join(sorted(result.extra))}")
|
||||
log(f" Expected: {', '.join(sorted(test.expected_value.split()))}")
|
||||
log(f" Actual: {', '.join(sorted(result.actual))}")
|
||||
elif result.status == 'error':
|
||||
log(f" Error: {result.message}")
|
||||
else:
|
||||
log(f" Expected: {test.expected_value}")
|
||||
log(f" Actual: {result.actual}")
|
||||
|
||||
|
||||
def run_tests_with_result(executable_path: str, source_files: List[str], verbose: bool, interactive: bool, plugin_path: Optional[str], result_path: str) -> int:
|
||||
try:
|
||||
exit_code = run_tests(executable_path, source_files,
|
||||
verbose, interactive, plugin_path)
|
||||
except Exception as e:
|
||||
log(f"An error occurred during test execution: {str(e)}")
|
||||
exit_code = 2 # Use a different exit code for unexpected errors
|
||||
|
||||
try:
|
||||
with open(result_path, 'w', encoding='utf-8') as f:
|
||||
f.write(str(exit_code))
|
||||
except IOError as e:
|
||||
log(f"Error writing result to file {result_path}: {str(e)}")
|
||||
# If we can't write to the file, we should still return the exit code
|
||||
|
||||
return exit_code
|
||||
|
||||
|
||||
def main() -> None:
|
||||
log(sys.argv)
|
||||
parser = argparse.ArgumentParser(
|
||||
description="LLDB 18 Debug Script with DWARF 5 Support")
|
||||
parser.add_argument("executable", help="Path to the executable")
|
||||
parser.add_argument("sources", nargs='+', help="Paths to the source files")
|
||||
parser.add_argument("-v", "--verbose", action="store_true",
|
||||
help="Enable verbose output")
|
||||
parser.add_argument("-i", "--interactive", action="store_true",
|
||||
help="Enable interactive mode on test failure")
|
||||
parser.add_argument("--plugin", help="Path to the LLDB plugin")
|
||||
parser.add_argument("--result-path", help="Path to write the result")
|
||||
args = parser.parse_args()
|
||||
|
||||
plugin_path = args.plugin or os.path.join(os.path.dirname(
|
||||
os.path.realpath(__file__)), "go_lldb_plugin.py")
|
||||
|
||||
try:
|
||||
if args.result_path:
|
||||
exit_code = run_tests_with_result(args.executable, args.sources,
|
||||
args.verbose, args.interactive, plugin_path, args.result_path)
|
||||
else:
|
||||
exit_code = run_tests(args.executable, args.sources,
|
||||
args.verbose, args.interactive, plugin_path)
|
||||
except Exception as e:
|
||||
log(f"An unexpected error occurred: {str(e)}")
|
||||
exit_code = 2 # Use a different exit code for unexpected errors
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user