Skip to content

Commit 44c2b67

Browse files
committed
Refactor go + python code to share more, and put stuff in the right place
1 parent 78c89fd commit 44c2b67

9 files changed

Lines changed: 128 additions & 111 deletions

File tree

‎elf.cc‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,19 @@ Object::getInterpreter() const
394394
return "";
395395
}
396396

397+
string
398+
Object::getMachineName() const
399+
{
400+
switch (elfHeader.e_machine) {
401+
case EM_386: return "i386";
402+
case EM_X86_64: return "x86_64";
403+
case EM_AARCH64: return "aarch64";
404+
case EM_ARM: return "arm";
405+
case EM_RISCV: return "riscv";
406+
default: return "machine-" + std::to_string(elfHeader.e_machine);
407+
}
408+
}
409+
397410
Elf::Object::sptr Object::debugData() const {
398411
if (debugData_ == nullptr) {
399412
if (lzmaAvailable()) {

‎golang.cc‎

Lines changed: 13 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,9 @@
11
#include "libpstack/go.h"
22
#include "libpstack/proc.h"
33

4-
#include <algorithm>
5-
#include <fstream>
64
#include <limits>
75

8-
9-
namespace pstack::Go {
10-
void
11-
Go::RuntimeOffsets::parse(std::istream &in) {
12-
Go::RuntimeOffsets offsets;
13-
14-
// map from key to function to set the content in offsets. The bool is
15-
// whether this field is mandatory. We remove each field as we parse it, so
16-
// if there are any mandatory ones left at the end, it's an error.
17-
//
18-
std::map<std::string_view, std::pair<bool, std::function<void()>>> m = {
19-
{ "version", {true, [&]() { version = parseString(in); } }},
20-
{ "machine", {true, [&]() { machine = parseInt<Elf::Half>(in); } }},
21-
{ "pointer_size", {true, [&]() { pointerSize = parseInt<size_t>(in); } }},
22-
{ "allgs_data", {true, [&]() { allgsData = parseInt<uintmax_t>(in); } }},
23-
{ "allgs_length", {true, [&]() { allgsLength = parseInt<uintmax_t>(in); } }},
24-
{ "allgs_stride", {true, [&]() { allgsStride = parseInt<uintmax_t>(in); } }},
25-
{ "g_size", {true, [&]() { gSize = parseInt<uintmax_t>(in); } }},
26-
{ "g_sched", {true, [&]() { gSched = parseInt<uintmax_t>(in); } }},
27-
{ "g_goid", {true, [&]() { gGoid = parseInt<uintmax_t>(in); } }},
28-
{ "gobuf_sp", {true, [&]() { gobufSp = parseInt<uintmax_t>(in); } }},
29-
{ "gobuf_pc", {true, [&]() { gobufPc = parseInt<uintmax_t>(in); } }},
30-
{ "gobuf_bp", {false, [&]() { gobufBp = parseInt<uintmax_t>(in); } }},
31-
{ "gobuf_g", {false, [&]() { gobufG = parseInt<uintmax_t>(in); } }},
32-
{ "gobuf_ctxt", {false, [&]() { gobufCtxt = parseInt<uintmax_t>(in); } }},
33-
{ "gobuf_lr", {false, [&]() { gobufLr = parseInt<uintmax_t>(in); } }},
34-
};
35-
36-
parseObject(in, [&](std::istream &is, std::string field) {
37-
auto node = m.extract(field);
38-
if (node) {
39-
node.mapped().second();
40-
} else {
41-
parseValue(is);
42-
}});
43-
44-
if (std::any_of(m.begin(), m.end(), [](const auto value) { return value.second.second; } ) ) {
45-
throw Exception() << "missing go fields";
46-
}
47-
}
48-
49-
}
50-
51-
526
namespace pstack::Procman {
53-
namespace {
547

558
// per-platform definitions for GREG and CTXTREG (Go's "g" and "ctxt" registers)
569
#if defined(__x86_64__)
@@ -65,74 +18,50 @@ namespace {
6518
#define CTXTREG 7
6619
#endif
6720

68-
Go::RuntimeOffsets
69-
loadOffsets(Process &proc, const std::string &version, Elf::Half machine)
70-
{
71-
const auto fileName = Go::offsetFileName(version, machine);
72-
for (const auto &directory : findXdgDataDirs()) {
73-
auto path = directory / fileName;
74-
std::ifstream in(path);
75-
if (!in)
76-
continue;
77-
Go::RuntimeOffsets offsets;
78-
offsets.parse(in);
79-
if (Go::versionSeries(offsets.version) != Go::versionSeries(version) ||
80-
offsets.machine != machine)
81-
throw Exception() << "Go offset data in " << path << " does not match its filename";
82-
if (offsets.pointerSize != sizeof(Elf::Addr))
83-
throw Exception() << "Go offset data in " << path << " has the wrong pointer size";
84-
if (proc.context.verbose)
85-
*proc.context.debug << "found Go offsets data in " << path << "\n";
86-
return offsets;
87-
}
88-
throw Exception() << "cannot find '" << fileName << "' - run pstack-mkgooff on a Go executable built with this Go version";
89-
}
90-
91-
} // namespace
92-
9321
void
9422
addGoRoutines(Process &proc, Stacks &stacks)
9523
{
9624
Elf::Object::sptr elf;
9725
Elf::Addr loadAddress;
9826
Elf::Sym allgsSymbol;
9927
try {
100-
std::tie(elf, loadAddress, allgsSymbol) =
101-
proc.resolveSymbolDetail("runtime.allgs", true);
28+
std::tie(elf, loadAddress, allgsSymbol) = proc.resolveSymbolDetail("runtime.allgs", true);
10229
}
103-
catch (const Exception &) {
30+
catch (const Exception &ex) {
10431
// Most targets are not Go programs; absence of this runtime symbol is normal.
32+
if (proc.context.verbose > 1)
33+
*proc.context.debug << "no go interpreter detected: " << ex.what() << "\n";
10534
return;
10635
}
10736

10837
try {
109-
const std::string goVersion = Go::version(*elf);
110-
const auto offsets = loadOffsets(proc, goVersion, elf->getHeader().e_machine);
38+
const auto &offsets = Go::getOffsets(proc.context, *elf);
11139
const Elf::Addr allgsAddress = loadAddress + allgsSymbol.st_value;
11240
const Elf::Addr allgsData = proc.io->readObj<Elf::Addr>(allgsAddress + offsets.allgsData);
11341
const Elf::Addr count = proc.io->readObj<Elf::Addr>(allgsAddress + offsets.allgsLength);
114-
if (count > 1'000'000)
115-
throw Exception() << "runtime.allgs has implausible length " << count;
11642
if (!allgsData || !count)
11743
return;
44+
if (count > 1'000'000)
45+
throw Exception() << "runtime.allgs has implausible length " << count;
46+
47+
static constexpr lwpid_t syntheticID = std::numeric_limits<lwpid_t>::min();
11848

119-
lwpid_t syntheticID = std::numeric_limits<lwpid_t>::min();
12049
for (Elf::Addr i = 0; i < count; ++i) {
12150
try {
122-
const Elf::Addr g = proc.io->readObj<Elf::Addr>(allgsData + i * offsets.allgsStride);
51+
auto g = proc.io->readObj<Elf::Addr>(allgsData + i * offsets.allgsStride);
12352
if (!g)
12453
continue;
12554
const Elf::Addr sched = g + offsets.gSched;
12655

12756
// Running goroutines have their state in a kernel thread. A nonzero
12857
// saved SP identifies goroutines parked in the runtime scheduler.
129-
const Elf::Addr sp = proc.io->readObj<Elf::Addr>(sched + offsets.gobufSp);
58+
auto sp = proc.io->readObj<Elf::Addr>(sched + offsets.gobufSp);
13059
if (!sp)
13160
continue;
132-
const Elf::Addr pc = proc.io->readObj<Elf::Addr>(sched + offsets.gobufPc);
61+
auto pc = proc.io->readObj<Elf::Addr>(sched + offsets.gobufPc);
13362
if (!pc)
13463
continue;
135-
const uint64_t goid = proc.io->readObj<uint64_t>(g + offsets.gGoid);
64+
auto goid = proc.io->readObj<uint64_t>(g + offsets.gGoid);
13665
if (!goid)
13766
continue; // g0 and gsignal are runtime scheduler stacks.
13867

‎gooffsets.cc‎

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
#include "libpstack/go.h"
2+
#include "libpstack/context.h"
23

34
#include <algorithm>
45
#include <array>
56
#include <cctype>
7+
#include <fstream>
8+
#include <functional>
69

710
namespace pstack::Go {
811
namespace {
@@ -128,20 +131,46 @@ readOldBuildInfoString(const Elf::Object &elf, uint64_t headerAddress, size_t po
128131
return value;
129132
}
130133

131-
std::string
132-
machineName(Elf::Half machine)
133-
{
134-
switch (machine) {
135-
case EM_386: return "i386";
136-
case EM_X86_64: return "x86_64";
137-
case EM_AARCH64: return "aarch64";
138-
case EM_ARM: return "arm";
139-
case EM_RISCV: return "riscv";
140-
default: return "machine-" + std::to_string(machine);
141-
}
142134
}
143135

144-
} // namespace
136+
void
137+
RuntimeOffsets::parse(std::istream &in) {
138+
RuntimeOffsets offsets;
139+
140+
// map from key to function to set the content in offsets. The bool is
141+
// whether this field is mandatory. We remove each field as we parse it, so
142+
// if there are any mandatory ones left at the end, it's an error.
143+
std::map<std::string_view, std::pair<bool, std::function<void()>>> m = {
144+
{ "version", {true, [&]() { version = parseString(in); } }},
145+
{ "machine", {true, [&]() { machine = parseString(in); } }},
146+
{ "pointer_size", {true, [&]() { pointerSize = parseInt<size_t>(in); } }},
147+
{ "allgs_data", {true, [&]() { allgsData = parseInt<uintmax_t>(in); } }},
148+
{ "allgs_length", {true, [&]() { allgsLength = parseInt<uintmax_t>(in); } }},
149+
{ "allgs_stride", {true, [&]() { allgsStride = parseInt<uintmax_t>(in); } }},
150+
{ "g_size", {true, [&]() { gSize = parseInt<uintmax_t>(in); } }},
151+
{ "g_sched", {true, [&]() { gSched = parseInt<uintmax_t>(in); } }},
152+
{ "g_goid", {true, [&]() { gGoid = parseInt<uintmax_t>(in); } }},
153+
{ "gobuf_sp", {true, [&]() { gobufSp = parseInt<uintmax_t>(in); } }},
154+
{ "gobuf_pc", {true, [&]() { gobufPc = parseInt<uintmax_t>(in); } }},
155+
{ "gobuf_bp", {false, [&]() { gobufBp = parseInt<uintmax_t>(in); } }},
156+
{ "gobuf_g", {false, [&]() { gobufG = parseInt<uintmax_t>(in); } }},
157+
{ "gobuf_ctxt", {false, [&]() { gobufCtxt = parseInt<uintmax_t>(in); } }},
158+
{ "gobuf_lr", {false, [&]() { gobufLr = parseInt<uintmax_t>(in); } }},
159+
};
160+
161+
parseObject(in, [&](std::istream &is, std::string field) {
162+
auto node = m.extract(field);
163+
if (node) {
164+
node.mapped().second();
165+
} else {
166+
parseValue(is);
167+
}});
168+
169+
if (std::any_of(m.begin(), m.end(), [](const auto value) { return value.second.second; } ) ) {
170+
throw Exception() << "missing go fields";
171+
}
172+
}
173+
// namespace
145174

146175
std::string
147176
versionSeries(std::string_view goVersion)
@@ -165,9 +194,52 @@ versionSeries(std::string_view goVersion)
165194
}
166195

167196
std::string
168-
offsetFileName(std::string_view goVersion, Elf::Half machine)
197+
offsetFileName(std::string_view goVersion, std::string_view machineName)
169198
{
170-
return "gooff-" + versionSeries(goVersion) + "-" + machineName(machine) + ".json";
199+
return "gooff-" + versionSeries(goVersion) + "-" + std::string(machineName) + ".json";
200+
}
201+
202+
namespace {
203+
204+
RuntimeOffsets
205+
loadOffsets(Context &context, const std::string &version, const std::string &machineName)
206+
{
207+
const auto fileName = offsetFileName(version, machineName);
208+
for (const auto &directory : findXdgDataDirs()) {
209+
auto path = directory / fileName;
210+
std::ifstream in(path);
211+
if (!in)
212+
continue;
213+
RuntimeOffsets offsets;
214+
offsets.parse(in);
215+
if (versionSeries(offsets.version) != versionSeries(version) ||
216+
offsets.machine != machineName)
217+
throw Exception() << "Go offset data in " << path << " does not match its filename";
218+
if (offsets.pointerSize != sizeof(Elf::Addr))
219+
throw Exception() << "Go offset data in " << path << " has the wrong pointer size";
220+
if (context.verbose)
221+
*context.debug << "found Go offsets data in " << path << "\n";
222+
return offsets;
223+
}
224+
throw Exception() << "cannot find '" << fileName << "' - run pstack-mkgooff on a Go executable built with this Go version";
225+
}
226+
227+
}
228+
229+
const RuntimeOffsets &
230+
getOffsets(Context &context, const Elf::Object &elf)
231+
{
232+
static std::map<std::string, RuntimeOffsets> allOffsets;
233+
234+
const std::string goVersion = version(elf);
235+
const std::string machineName = elf.getMachineName();
236+
const std::string key = offsetFileName(goVersion, machineName);
237+
238+
auto iter = allOffsets.find(key);
239+
if (iter != allOffsets.end())
240+
return iter->second;
241+
auto offsets = loadOffsets(context, goVersion, machineName);
242+
return allOffsets.emplace(key, std::move(offsets)).first->second;
171243
}
172244

173245
std::string
@@ -209,7 +281,7 @@ version(const Elf::Object &elf)
209281

210282
RuntimeOffsets
211283
runtimeOffsets(const Dwarf::Info::sptr &dwarf, std::string goVersion,
212-
Elf::Half machine, size_t pointerSize)
284+
std::string machine, size_t pointerSize)
213285
{
214286
DIE g = findType(dwarf, "runtime.g");
215287
auto gSize = g ? uintmax_t(g.attribute(Dwarf::DW_AT_byte_size)) : 0;
@@ -237,7 +309,7 @@ runtimeOffsets(const Dwarf::Info::sptr &dwarf, std::string goVersion,
237309

238310
RuntimeOffsets offsets {
239311
.version = std::move(goVersion),
240-
.machine = machine,
312+
.machine = std::move(machine),
241313
.pointerSize = pointerSize,
242314
.allgsData = 0,
243315
.allgsLength = pointerSize,

‎libpstack/elf.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ class Object : public std::enable_shared_from_this<Object> {
322322

323323
// Misc operations
324324
std::string getInterpreter() const;
325+
std::string getMachineName() const;
325326
const Ehdr &getHeader() const { return elfHeader; }
326327
const Phdr *getSegmentForAddress(Off) const;
327328
Notes notes() const { return Notes(this); }

‎libpstack/go.h‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace pstack::Go {
1111

1212
struct RuntimeOffsets {
1313
std::string version;
14-
Elf::Half machine{};
14+
std::string machine;
1515
size_t pointerSize{};
1616
uintmax_t allgsData{};
1717
uintmax_t allgsLength{};
@@ -28,11 +28,12 @@ struct RuntimeOffsets {
2828
void parse(std::istream &);
2929
};
3030

31-
std::string offsetFileName(std::string_view version, Elf::Half machine);
31+
std::string offsetFileName(std::string_view version, std::string_view machineName);
3232
std::string versionSeries(std::string_view version);
3333
std::string version(const Elf::Object &);
34+
const RuntimeOffsets &getOffsets(Context &, const Elf::Object &);
3435
RuntimeOffsets runtimeOffsets(const Dwarf::Info::sptr &, std::string version,
35-
Elf::Half machine, size_t pointerSize);
36+
std::string machine, size_t pointerSize);
3637

3738
} // namespace pstack::Go
3839

‎libpstack/pyrdb.h‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <set>
44
#include <sstream>
55
#include <string>
6+
#include <utility>
67
#include <vector>
78
#include <variant>
89

@@ -11,13 +12,13 @@ namespace pstack::Py {
1112
class Version {
1213
public:
1314
inline std::string offsetFileName();
14-
Version(unsigned long data, Elf::Half machine) : data(data), machine(machine) {}
15+
Version(unsigned long data, std::string machineName) : data(data), machineName(std::move(machineName)) {}
1516
Version() = delete;
1617
operator bool() const { return data != 0; }
1718
auto operator <=> (const Version &rhs) const { return data <=> rhs.data; }
1819
private:
1920
unsigned long data;
20-
Elf::Half machine;
21+
std::string machineName;
2122
friend std::string to_string(Version pv);
2223
};
2324

@@ -28,7 +29,7 @@ inline std::string to_string(Version pv) {
2829
std::to_string((pv.data >> 16) & 0xff ) + "." +
2930
std::to_string((pv.data >> 8) & 0xff ) + phase +
3031
"-" +
31-
(pv.machine == EM_386 ? "i386" : pv.machine == EM_X86_64 ? "x86_64" : pv.machine == EM_AARCH64 ? "aarch64" : "unknown");
32+
pv.machineName;
3233
}
3334

3435
std::string Version::offsetFileName()

‎mkgooff.cc‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ int main(int argc, char *argv[])
1919
auto goVersion = pstack::Go::version(*elf);
2020
auto dwarf = context.findDwarf(elf);
2121
auto offsets = pstack::Go::runtimeOffsets(
22-
dwarf, goVersion, elf->getHeader().e_machine, sizeof(pstack::Elf::Addr));
22+
dwarf, goVersion, elf->getMachineName(), sizeof(pstack::Elf::Addr));
2323

24-
const auto fileName = pstack::Go::offsetFileName(goVersion, elf->getHeader().e_machine);
24+
const auto fileName = pstack::Go::offsetFileName(goVersion, elf->getMachineName());
2525
std::ofstream out(fileName);
2626
if (!out)
2727
throw pstack::Exception() << "cannot create " << fileName;

‎mkpyoff.cc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ void generateOne(Context &ctx, std::filesystem::path path) {
167167
auto fileOff = sym.st_value - phdr->p_vaddr + phdr->p_offset;
168168
elf->io->readObj(fileOff, &pyv);
169169

170-
Py::Version version(pyv, elf->getHeader().e_machine);
170+
Py::Version version(pyv, elf->getMachineName());
171171

172172
std::filesystem::path outfileName { version.offsetFileName() };
173173
std::ofstream out(outfileName);

0 commit comments

Comments
 (0)