Skip to content

Commit ffcb9cc

Browse files
committed
libdrgn: debug_info: implement creating objects from DWARF location descriptions
Add support for evaluating a DWARF location description and translating it into a drgn object. In this commit, this is just used for global variables, but an upcoming commit will wire this up to stack traces for parameters and local variables. There are a few locations that drgn's object model can't represent yet. DW_OP_piece/DW_OP_bit_piece can describe objects that are only partially known or partially in memory; we approximate these where we can. We don't have a good way to support DW_OP_implicit_pointer at all yet. This also adds test cases for DWARF expressions, which we couldn't easily test before. Signed-off-by: Omar Sandoval <[email protected]>
1 parent 8335450 commit ffcb9cc

File tree

9 files changed

+2200
-96
lines changed

9 files changed

+2200
-96
lines changed

libdrgn/debug_info.c

Lines changed: 438 additions & 53 deletions
Large diffs are not rendered by default.

libdrgn/object.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ drgn_object_set_from_buffer(struct drgn_object *res,
318318
bit_offset);
319319
}
320320

321-
static struct drgn_error *
321+
struct drgn_error *
322322
drgn_object_set_reference_internal(struct drgn_object *res,
323323
const struct drgn_object_type *type,
324324
uint64_t address, uint64_t bit_offset)

libdrgn/object.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
#ifndef DRGN_OBJECT_H
1313
#define DRGN_OBJECT_H
1414

15+
#include <stdlib.h>
16+
#include <string.h>
17+
1518
#include "drgn.h"
1619
#include "type.h"
1720

@@ -31,6 +34,24 @@
3134
* @{
3235
*/
3336

37+
/** Allocate a zero-initialized @ref drgn_value. */
38+
static inline bool drgn_value_zalloc(uint64_t size, union drgn_value *value_ret,
39+
char **buf_ret)
40+
{
41+
if (size <= sizeof(value_ret->ibuf)) {
42+
memset(value_ret->ibuf, 0, sizeof(value_ret->ibuf));
43+
*buf_ret = value_ret->ibuf;
44+
} else {
45+
if (size > SIZE_MAX)
46+
return false;
47+
char *buf = calloc(1, size);
48+
if (!buf)
49+
return false;
50+
value_ret->bufp = *buf_ret = buf;
51+
}
52+
return true;
53+
}
54+
3455
/**
3556
* Get whether an object is zero.
3657
*
@@ -139,6 +160,15 @@ drgn_object_set_from_buffer_internal(struct drgn_object *res,
139160
const struct drgn_object_type *type,
140161
const void *buf, uint64_t bit_offset);
141162

163+
/**
164+
* Like @ref drgn_object_set_reference() but @ref drgn_object_type() was already
165+
* called.
166+
*/
167+
struct drgn_error *
168+
drgn_object_set_reference_internal(struct drgn_object *res,
169+
const struct drgn_object_type *type,
170+
uint64_t address, uint64_t bit_offset);
171+
142172
/**
143173
* Binary operator implementation.
144174
*

libdrgn/serialize.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,86 @@
66

77
#include "serialize.h"
88

9+
static inline uint8_t copy_bits_step(const uint8_t *s, unsigned int src_bit_offset,
10+
unsigned int bit_size,
11+
unsigned int dst_bit_offset, bool lsb0)
12+
{
13+
uint8_t result;
14+
if (lsb0) {
15+
result = s[0] >> src_bit_offset;
16+
if (bit_size > 8 - src_bit_offset)
17+
result |= s[1] << (8 - src_bit_offset);
18+
result <<= dst_bit_offset;
19+
} else {
20+
result = s[0] << src_bit_offset;
21+
if (bit_size > 8 - src_bit_offset)
22+
result |= s[1] >> (8 - src_bit_offset);
23+
result >>= dst_bit_offset;
24+
}
25+
return result;
26+
}
27+
28+
void copy_bits(void *dst, unsigned int dst_bit_offset, const void *src,
29+
unsigned int src_bit_offset, uint64_t bit_size, bool lsb0)
30+
{
31+
assert(dst_bit_offset < 8);
32+
assert(src_bit_offset < 8);
33+
34+
if (bit_size == 0)
35+
return;
36+
37+
uint8_t *d = dst;
38+
const uint8_t *s = src;
39+
uint64_t dst_last_bit = dst_bit_offset + bit_size - 1;
40+
uint8_t dst_first_mask = copy_bits_first_mask(dst_bit_offset, lsb0);
41+
uint8_t dst_last_mask = copy_bits_last_mask(dst_last_bit, lsb0);
42+
43+
if (dst_bit_offset == src_bit_offset) {
44+
/*
45+
* In the common case that the source and destination have the
46+
* same offset, we can use memcpy(), preserving bits at the
47+
* start and/or end if necessary.
48+
*/
49+
uint8_t first_byte = d[0];
50+
uint8_t last_byte = d[dst_last_bit / 8];
51+
memcpy(d, s, dst_last_bit / 8 + 1);
52+
if (dst_bit_offset != 0) {
53+
d[0] = ((first_byte & ~dst_first_mask)
54+
| (d[0] & dst_first_mask));
55+
}
56+
if (dst_last_bit % 8 != 7) {
57+
d[dst_last_bit / 8] = ((last_byte & ~dst_last_mask)
58+
| (d[dst_last_bit / 8] & dst_last_mask));
59+
}
60+
} else if (bit_size <= 8 - dst_bit_offset) {
61+
/* Destination is only one byte. */
62+
uint8_t dst_mask = dst_first_mask & dst_last_mask;
63+
d[0] = ((d[0] & ~dst_mask)
64+
| (copy_bits_step(&s[0], src_bit_offset, bit_size,
65+
dst_bit_offset, lsb0) & dst_mask));
66+
} else {
67+
/* Destination is two or more bytes. */
68+
d[0] = ((d[0] & ~dst_first_mask)
69+
| (copy_bits_step(&s[0], src_bit_offset,
70+
8 - dst_bit_offset, dst_bit_offset,
71+
lsb0) & dst_first_mask));
72+
src_bit_offset += 8 - dst_bit_offset;
73+
size_t si = src_bit_offset / 8;
74+
src_bit_offset %= 8;
75+
size_t di = 1;
76+
while (di < dst_last_bit / 8) {
77+
d[di] = copy_bits_step(&s[si], src_bit_offset, 8, 0,
78+
lsb0);
79+
di++;
80+
si++;
81+
}
82+
d[di] = ((d[di] & ~dst_last_mask)
83+
| (copy_bits_step(&s[si], src_bit_offset,
84+
dst_last_bit % 8 + 1, 0, lsb0)
85+
& dst_last_mask));
86+
}
87+
}
88+
989
void serialize_bits(void *buf, uint64_t bit_offset, uint64_t uvalue,
1090
uint8_t bit_size, bool little_endian)
1191
{

libdrgn/serialize.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,45 @@ static inline void copy_lsbytes(void *dst, size_t dst_size,
7373
}
7474
}
7575

76+
/**
77+
* Return a bit mask with bits `[bit_offset, 7]` set.
78+
*
79+
* @param[in] lsb0 See @ref copy_bits().
80+
*/
81+
static inline uint8_t copy_bits_first_mask(unsigned int bit_offset, bool lsb0)
82+
{
83+
return lsb0 ? 0xff << bit_offset : 0xff >> bit_offset;
84+
}
85+
86+
/**
87+
* Return a bit mask with bits `[0, last_bit % 8]` set.
88+
*
89+
* @param[in] lsb0 See @ref copy_bits().
90+
*/
91+
static inline uint8_t copy_bits_last_mask(uint64_t last_bit, bool lsb0)
92+
{
93+
return lsb0 ? 0xff >> (7 - last_bit % 8) : 0x7f80 >> (last_bit % 8);
94+
}
95+
96+
/**
97+
* Copy @p bit_size bits from @p src at bit offset @p src_bit_offset to @p dst
98+
* at bit offset @p dst_bit_offset.
99+
*
100+
* @param[in] dst Destination buffer.
101+
* @param[in] dst_bit_offset Offset in bits from the beginning of @p dst to copy
102+
* to. Must be < 8.
103+
* @param[in] src Source buffer.
104+
* @param[in] src_bit_offset Offset in bits from the beginning of @p src to copy
105+
* from. Must be < 8.
106+
* @param[in] bit_size Number of bits to copy.
107+
* @param[in] lsb0 If @c true, bits within a byte are numbered from least
108+
* significant (0) to most significant (7); if @c false, they are numbered from
109+
* most significant (0) to least significant (7). This determines the
110+
* interpretation of @p dst_bit_offset and @p src_bit_offset.
111+
*/
112+
void copy_bits(void *dst, unsigned int dst_bit_offset, const void *src,
113+
unsigned int src_bit_offset, uint64_t bit_size, bool lsb0);
114+
76115
/**
77116
* Serialize bits to a memory buffer.
78117
*

tests/__init__.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,23 @@ def mock_memory_read(data, address, count, offset, physical):
4141
return data[offset : offset + count]
4242

4343

44+
def add_mock_memory_segments(prog, segments):
45+
for segment in segments:
46+
if segment.virt_addr is not None:
47+
prog.add_memory_segment(
48+
segment.virt_addr,
49+
len(segment.buf),
50+
functools.partial(mock_memory_read, segment.buf),
51+
)
52+
if segment.phys_addr is not None:
53+
prog.add_memory_segment(
54+
segment.phys_addr,
55+
len(segment.buf),
56+
functools.partial(mock_memory_read, segment.buf),
57+
True,
58+
)
59+
60+
4461
class MockObject(NamedTuple):
4562
name: str
4663
type: Type
@@ -84,20 +101,7 @@ def mock_object_find(prog, name, flags, filename):
84101

85102
prog = Program(platform)
86103
if segments is not None:
87-
for segment in segments:
88-
if segment.virt_addr is not None:
89-
prog.add_memory_segment(
90-
segment.virt_addr,
91-
len(segment.buf),
92-
functools.partial(mock_memory_read, segment.buf),
93-
)
94-
if segment.phys_addr is not None:
95-
prog.add_memory_segment(
96-
segment.phys_addr,
97-
len(segment.buf),
98-
functools.partial(mock_memory_read, segment.buf),
99-
True,
100-
)
104+
add_mock_memory_segments(prog, segments)
101105
if types is not None:
102106
prog.add_type_finder(mock_find_type)
103107
if objects is not None:

tests/assembler.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Copyright (c) Facebook, Inc. and its affiliates.
2+
# SPDX-License-Identifier: GPL-3.0-or-later
3+
4+
from collections import namedtuple
5+
6+
7+
def _append_uleb128(buf, value):
8+
while True:
9+
byte = value & 0x7F
10+
value >>= 7
11+
if value:
12+
buf.append(byte | 0x80)
13+
else:
14+
buf.append(byte)
15+
break
16+
17+
18+
def _append_sleb128(buf, value):
19+
while True:
20+
byte = value & 0x7F
21+
value >>= 7
22+
if (not value and not (byte & 0x40)) or (value == -1 and (byte & 0x40)):
23+
buf.append(byte)
24+
break
25+
else:
26+
buf.append(byte | 0x80)
27+
28+
29+
U8 = namedtuple("U8", ["value"])
30+
U8._append = lambda self, buf, byteorder: buf.append(self.value)
31+
S8 = namedtuple("S8", ["value"])
32+
S8._append = lambda self, buf, byteorder: buf.append(self.value & 0xFF)
33+
U16 = namedtuple("U16", ["value"])
34+
U16._append = lambda self, buf, byteorder: buf.extend(self.value.to_bytes(2, byteorder))
35+
S16 = namedtuple("S16", ["value"])
36+
S16._append = lambda self, buf, byteorder: buf.extend(
37+
self.value.to_bytes(2, byteorder, signed=True)
38+
)
39+
U32 = namedtuple("U32", ["value"])
40+
U32._append = lambda self, buf, byteorder: buf.extend(self.value.to_bytes(4, byteorder))
41+
S32 = namedtuple("S32", ["value"])
42+
S32._append = lambda self, buf, byteorder: buf.extend(
43+
self.value.to_bytes(4, byteorder, signed=True)
44+
)
45+
U64 = namedtuple("U64", ["value"])
46+
U64._append = lambda self, buf, byteorder: buf.extend(self.value.to_bytes(8, byteorder))
47+
S64 = namedtuple("S64", ["value"])
48+
S64._append = lambda self, buf, byteorder: buf.extend(
49+
self.value.to_bytes(8, byteorder, signed=True)
50+
)
51+
ULEB128 = namedtuple("ULEB128", ["value"])
52+
ULEB128._append = lambda self, buf, byteorder: _append_uleb128(buf, self.value)
53+
SLEB128 = namedtuple("SLEB128", ["value"])
54+
SLEB128._append = lambda self, buf, byteorder: _append_sleb128(buf, self.value)
55+
56+
57+
def assemble(*args, little_endian=True):
58+
byteorder = "little" if little_endian else "big"
59+
buf = bytearray()
60+
for arg in args:
61+
arg._append(buf, byteorder)
62+
return buf

tests/dwarfwriter.py

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from collections import namedtuple
55
import os.path
66

7+
from tests.assembler import _append_sleb128, _append_uleb128
78
from tests.dwarf import DW_AT, DW_FORM, DW_TAG
89
from tests.elf import ET, PT, SHT
910
from tests.elfwriter import ElfSection, create_elf_file
@@ -13,28 +14,6 @@
1314
DwarfDie.__new__.__defaults__ = (None,)
1415

1516

16-
def _append_uleb128(buf, value):
17-
while True:
18-
byte = value & 0x7F
19-
value >>= 7
20-
if value:
21-
buf.append(byte | 0x80)
22-
else:
23-
buf.append(byte)
24-
break
25-
26-
27-
def _append_sleb128(buf, value):
28-
while True:
29-
byte = value & 0x7F
30-
value >>= 7
31-
if (not value and not (byte & 0x40)) or (value == -1 and (byte & 0x40)):
32-
buf.append(byte)
33-
break
34-
else:
35-
buf.append(byte | 0x80)
36-
37-
3817
def _compile_debug_abbrev(unit_dies, use_dw_form_indirect):
3918
buf = bytearray()
4019
code = 1

0 commit comments

Comments
 (0)