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
710namespace pstack ::Go {
811namespace {
@@ -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
146175std::string
147176versionSeries (std::string_view goVersion)
@@ -165,9 +194,52 @@ versionSeries(std::string_view goVersion)
165194}
166195
167196std::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
173245std::string
@@ -209,7 +281,7 @@ version(const Elf::Object &elf)
209281
210282RuntimeOffsets
211283runtimeOffsets (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,
0 commit comments