test(ptracer): add helper func to find readable memory

This commit is contained in:
ZQ 2025-02-11 13:45:35 +08:00
parent 9df6a51947
commit 25c32240cf

View File

@ -66,61 +66,66 @@ func createTestProcess(t *testing.T) (int, func()) {
return cmd.Process.Pid, cleanup return cmd.Process.Pid, cleanup
} }
// TestVmRead tests the vmRead function // findReadableMemoryRegion finds a readable memory region in the process's address space
func TestVmRead(t *testing.T) { func findReadableMemoryRegion(t *testing.T, pid int, minSize int) uintptr {
pid, cleanup := createTestProcess(t)
defer cleanup()
// create test data
testData := []byte("Hello, World!")
buff := make([]byte, len(testData))
// get process memory maps
maps, err := os.ReadFile(fmt.Sprintf("/proc/%d/maps", pid)) maps, err := os.ReadFile(fmt.Sprintf("/proc/%d/maps", pid))
if err != nil { if err != nil {
t.Fatalf("Failed to read process maps: %v", err) t.Fatalf("Failed to read process maps: %v", err)
} }
// parse memory maps to find a readable address
var addr uintptr
for _, line := range bytes.Split(maps, []byte{'\n'}) { for _, line := range bytes.Split(maps, []byte{'\n'}) {
if len(line) == 0 { if len(line) == 0 {
continue continue
} }
if bytes.Contains(line, []byte("r-x")) { // find a readable segment if bytes.Contains(line, []byte("r-x")) {
var start uint64 // Parse address range: start-end
fmt.Sscanf(string(line), "%x-", &start) var start, end uint64
addr = uintptr(start) _, err := fmt.Sscanf(string(line), "%x-%x", &start, &end)
break if err != nil {
continue
}
// Calculate region size
size := end - start
if size >= uint64(minSize) {
return uintptr(start)
}
} }
} }
if addr == 0 { t.Fatalf("Failed to find readable memory region with minimum size %d", minSize)
t.Fatal("Failed to find readable memory region") return 0
} }
// test reading // TestVmRead tests the vmRead function
n, err := vmRead(pid, addr, buff) func TestVmRead(t *testing.T) {
pid, cleanup := createTestProcess(t)
defer cleanup()
buff := make([]byte, 100)
// Ensure the found memory region is large enough
baseAddr := findReadableMemoryRegion(t, pid, len(buff))
n, err := vmRead(pid, baseAddr, buff)
if err != nil { if err != nil {
t.Fatalf("vmRead failed: %v", err) t.Errorf("vmRead() error = %v", err)
} }
if n == 0 { if n == 0 {
t.Error("vmRead returned 0 bytes") t.Error("vmRead returned 0 bytes")
} }
} }
// TestVmReadStr tests the vmReadStr function // vmReadStrTestCase defines a test case for vmReadStr
func TestVmReadStr(t *testing.T) { type vmReadStrTestCase struct {
pid, cleanup := createTestProcess(t) name string
defer cleanup() buffSize int
addrAlign uintptr // address alignment, used to test different alignment scenarios
wantErr bool
}
// test cases // getVmReadStrTestCases returns test cases for vmReadStr testing
testCases := []struct { func getVmReadStrTestCases() []vmReadStrTestCase {
name string return []vmReadStrTestCase{
buffSize int
addrAlign uintptr // address alignment, used to test different alignment scenarios
wantErr bool
}{
{ {
name: "small_buffer_aligned", name: "small_buffer_aligned",
buffSize: 10, buffSize: 10,
@ -158,53 +163,42 @@ func TestVmReadStr(t *testing.T) {
wantErr: false, wantErr: false,
}, },
} }
}
// TestVmReadStr tests the vmReadStr function
func TestVmReadStr(t *testing.T) {
pid, cleanup := createTestProcess(t)
defer cleanup()
testCases := getVmReadStrTestCases()
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
buff := make([]byte, tc.buffSize) buff := make([]byte, tc.buffSize)
// get a readable memory address // Ensure the found memory region is large enough
maps, err := os.ReadFile(fmt.Sprintf("/proc/%d/maps", pid)) baseAddr := findReadableMemoryRegion(t, pid, tc.buffSize)
if err != nil {
t.Fatalf("Failed to read process maps: %v", err)
}
var baseAddr uintptr // Use test case specified alignment offset
for _, line := range bytes.Split(maps, []byte{'\n'}) {
if len(line) == 0 {
continue
}
if bytes.Contains(line, []byte("r-x")) {
var start uint64
fmt.Sscanf(string(line), "%x-", &start)
baseAddr = uintptr(start)
break
}
}
if baseAddr == 0 {
t.Fatal("Failed to find readable memory region")
}
// use test case specified alignment offset
testAddr := baseAddr + tc.addrAlign testAddr := baseAddr + tc.addrAlign
// record buffer content before reading // Record buffer content before reading
originalBuff := make([]byte, len(buff)) originalBuff := make([]byte, len(buff))
copy(originalBuff, buff) copy(originalBuff, buff)
err = vmReadStr(pid, testAddr, buff) err := vmReadStr(pid, testAddr, buff)
if (err != nil) != tc.wantErr { if (err != nil) != tc.wantErr {
t.Errorf("vmReadStr() error = %v, wantErr %v", err, tc.wantErr) t.Errorf("vmReadStr() error = %v, wantErr %v", err, tc.wantErr)
} }
// verify if actual reading occurred // Verify reading results
if !bytes.Equal(buff, originalBuff) { if !bytes.Equal(buff, originalBuff) {
// at least some data was read // For vmReadStr, we only care that some data was read
// and that the function completed without error
t.Logf("Data was read successfully for case: %s", tc.name) t.Logf("Data was read successfully for case: %s", tc.name)
} }
// special case: check buffer size smaller than distance to boundary // Special case: check buffer size smaller than distance to boundary
if tc.name == "buffer_smaller_than_to_boundary" { if tc.name == "buffer_smaller_than_to_boundary" {
distToBoundary := pageSize - int(testAddr%uintptr(pageSize)) distToBoundary := pageSize - int(testAddr%uintptr(pageSize))
if distToBoundary > len(buff) { if distToBoundary > len(buff) {