History

SBL file reader. Create and implement basic symbol interface used during debugging.

arr committed 9 years ago

Commit a3c184b1 parent 3b54d824 files · +852 −1Browse files

Showing the changes to src/asm/sbl_reader.cpp (1 of 4 files). Show all

Unified · Split

src/asm/sbl_reader.cpp+710−0
@@ -0,0 +1,710 @@
1+//
2+// Dis VM
3+// File: sbl_reader.cpp
4+// Author: arr
5+//
6+
7+#include <cassert>
8+#include <string>
9+#include <vector>
10+#include <cerrno>
11+#include <memory>
12+#include <vm_asm.h>
13+#include <utils.h>
14+#include <buffered_reader.h>
15+
16+using namespace disvm;
17+using namespace disvm::symbol;
18+
19+namespace
20+{
21+ enum class sbl_version_t
22+ {
23+ unknown,
24+ sbl_2_0,
25+ sbl_2_1,
26+ };
27+
28+ struct header_t
29+ {
30+ sbl_version_t version_id;
31+ std::string module_name;
32+ };
33+
34+ using file_table_t = std::vector<std::string>;
35+
36+ struct sbl_read_context_t
37+ {
38+ int32_t pc_count;
39+ int32_t last_file;
40+ int32_t last_line;
41+ };
42+
43+ struct pc_item_t
44+ {
45+ source_ref_t source;
46+ int32_t debug_statement;
47+ };
48+
49+ using pc_table_t = std::vector<pc_item_t>;
50+
51+ struct type_t
52+ {
53+ type_class_t type_class;
54+ int32_t value_index_1;
55+ int32_t value_index_2;
56+ };
57+
58+ struct id_item_t
59+ {
60+ uint32_t offset;
61+ std::string name;
62+ source_ref_t source;
63+ type_t type;
64+ };
65+
66+ using id_table_t = std::vector<id_item_t>;
67+
68+ struct func_item_t
69+ {
70+ runtime::vm_pc_t entry_pc;
71+ runtime::vm_pc_t limit_pc;
72+ std::string name;
73+ id_table_t args;
74+ id_table_t locals;
75+ type_t return_type;
76+ };
77+
78+ using func_table_t = std::vector<func_item_t>;
79+
80+ struct name_source_size_t
81+ {
82+ std::string name;
83+ source_ref_t source;
84+ int32_t size_in_bytes;
85+ id_table_t id_table;
86+ std::unique_ptr<std::vector<name_source_size_t>> tag_table;
87+ };
88+
89+ using adt_item_t = name_source_size_t;
90+
91+ struct tuple_item_t
92+ {
93+ int32_t size_in_bytes;
94+ id_table_t id_table;
95+ };
96+
97+ struct type_table_t
98+ {
99+ static const uint32_t string_table_mask = 0x80000000;
100+ static const uint32_t inner_type_table_mask = 0x40000000;
101+ static const uint32_t source_table_mask = 0x20000000;
102+ static const uint32_t adt_table_mask = 0x10000000;
103+ static const uint32_t tuple_type_mask = 0x08000000;
104+ static const uint32_t types_mask = 0;
105+
106+ std::vector<type_t> types;
107+ std::vector<std::string> strings_table;
108+ std::vector<type_t> inner_type_table;
109+ std::vector<source_ref_t> source_table;
110+ std::vector<name_source_size_t> adt_table;
111+ std::vector<tuple_item_t> tuple_table;
112+
113+ template<typename T>
114+ const T &resolve_table_index(int32_t idx) const;
115+ };
116+
117+ template<>
118+ const std::string &type_table_t::resolve_table_index<std::string>(int32_t idx) const
119+ {
120+ const auto idx_local = idx & ~string_table_mask;
121+ if (0 > idx_local || idx_local >= strings_table.size())
122+ throw std::out_of_range{ "Invalid string table index" };
123+
124+ return strings_table[idx_local];
125+ }
126+
127+ template<>
128+ const type_t &type_table_t::resolve_table_index<type_t>(int32_t idx) const
129+ {
130+ const auto idx_local = idx & ~inner_type_table_mask;
131+ if (0 > idx_local || idx_local >= inner_type_table.size())
132+ throw std::out_of_range{ "Invalid inner type table index" };
133+
134+ return inner_type_table[idx_local];
135+ }
136+
137+ template<>
138+ const source_ref_t &type_table_t::resolve_table_index<source_ref_t>(int32_t idx) const
139+ {
140+ const auto idx_local = idx & ~source_table_mask;
141+ if (0 > idx_local || idx_local >= source_table.size())
142+ throw std::out_of_range{ "Invalid source table index" };
143+
144+ return source_table[idx_local];
145+ }
146+
147+ template<>
148+ const name_source_size_t &type_table_t::resolve_table_index<name_source_size_t>(int32_t idx) const
149+ {
150+ const auto idx_local = idx & ~adt_table_mask;
151+ if (0 > idx_local || idx_local >= adt_table.size())
152+ throw std::out_of_range{ "Invalid adt table index" };
153+
154+ return adt_table[idx_local];
155+ }
156+
157+ template<>
158+ const tuple_item_t &type_table_t::resolve_table_index<tuple_item_t>(int32_t idx) const
159+ {
160+ const auto idx_local = idx & ~tuple_type_mask;
161+ if (0 > idx_local || idx_local >= tuple_table.size())
162+ throw std::out_of_range{ "Invalid tuple table index" };
163+
164+ return tuple_table[idx_local];
165+ }
166+
167+ // sbl: header file-table pc-table type-table func-table data-table
168+ struct sbl_file_t final
169+ {
170+ header_t header;
171+
172+ file_table_t file_table;
173+ pc_table_t pc_table;
174+ type_table_t type_table;
175+ func_table_t func_table;
176+ id_table_t data_table;
177+ };
178+
179+ // header: <magic> '\n' module '\n'
180+ // module: <string>
181+ header_t read_header(util::buffered_reader_t &r)
182+ {
183+ header_t header;
184+ auto v = r.get_as_string_until('\n');
185+ if (v.compare("limbo .sbl 2.1") == 0)
186+ header.version_id = sbl_version_t::sbl_2_1;
187+ else if (v.compare("limbo .sbl 2.0") == 0)
188+ header.version_id = sbl_version_t::sbl_2_0;
189+ else
190+ throw std::runtime_error{ "Unknown symbol file version" };
191+
192+ header.module_name = r.get_as_string_until('\n');
193+
194+ return header;
195+ }
196+
197+ // file-table: <count> '\n' file*
198+ // file: <string> '\n'
199+ file_table_t read_file_table(util::buffered_reader_t &r)
200+ {
201+ auto count_str = r.get_as_string_until('\n');
202+
203+ file_table_t file_table;
204+ const auto count = std::stoi(count_str);
205+ file_table.resize(count);
206+ for (auto i = 0; i < count; ++i)
207+ {
208+ auto file = r.get_as_string_until('\n');
209+ file_table[i] = std::move(file);
210+ }
211+
212+ return file_table;
213+ }
214+
215+ // src: pos ',' pos
216+ // pos: file':'? line'.'? char
217+ // file: <int>
218+ // line: <int>
219+ // char: <int>
220+ source_ref_t read_src_entry(const std::string &src_str, sbl_read_context_t &fc)
221+ {
222+ auto src_parts = util::split(src_str, ",");
223+ if (src_parts.size() != 2)
224+ throw std::runtime_error{ "Invalid source item" };
225+
226+ source_ref_t src;
227+
228+ // Initialize the source with the last state
229+ src.source_id = fc.last_file;
230+ src.begin_line = fc.last_line;
231+ src.end_line = fc.last_line;
232+ for (auto i = std::size_t{ 0 }; i < src_parts.size(); ++i)
233+ {
234+ const auto &pos = src_parts[i];
235+ const auto len = pos.length();
236+
237+ // Parse position
238+ for (auto j = std::size_t{ 0 }; j < len; ++j)
239+ {
240+ auto consumed = std::size_t{ 0 };
241+ auto v = std::stoi(pos.data() + j, &consumed);
242+ j += consumed;
243+ if (j >= len)
244+ {
245+ if (i == 0)
246+ src.begin_column = v;
247+
248+ src.end_column = v;
249+ }
250+ else if (pos[j] == '.')
251+ {
252+ if (i == 0)
253+ src.begin_line = v;
254+
255+ src.end_line = v;
256+ fc.last_line = v;
257+ }
258+ else if (pos[j] == ':')
259+ {
260+ assert((i == 0 || src.source_id == v) && "Source position should not change across statements");
261+ src.source_id = v;
262+ fc.last_file = v;
263+ }
264+ }
265+ }
266+
267+ return src;
268+ }
269+
270+ // pc-table: <count> '\n' pc-item*
271+ // pc-item: src ' ' stmt '\n'
272+ // stmt: <int>
273+ pc_table_t read_pc_table(util::buffered_reader_t &r, sbl_read_context_t &fc)
274+ {
275+ auto count_str = r.get_as_string_until('\n');
276+
277+ uint32_t last_source = 0;
278+ uint32_t last_line = 0;
279+
280+ pc_table_t pc_table;
281+ const auto count = std::stoi(count_str);
282+ pc_table.resize(count);
283+ for (auto i = 0; i < count; ++i)
284+ {
285+ auto item = r.get_as_string_until('\n');
286+
287+ auto pc_item_parts = util::split(item);
288+ if (pc_item_parts.size() != 2)
289+ throw std::runtime_error{ "Invalid PC item" };
290+
291+ pc_item_t curr_item;
292+ curr_item.source = read_src_entry(pc_item_parts[0], fc);
293+ curr_item.debug_statement = std::stoi(pc_item_parts[1]);
294+
295+ // Record the current source and line ID since those are the new default values
296+ last_source = curr_item.source.source_id;
297+ last_line = curr_item.source.end_line;
298+
299+ pc_table[i] = std::move(curr_item);
300+ }
301+
302+ fc.pc_count = count;
303+ return pc_table;
304+ }
305+
306+ // Forward declaration
307+ type_t read_type(util::buffered_reader_t &, type_table_t &, sbl_read_context_t &);
308+
309+ // id-table: <count> '\n' id-item*
310+ // id-item: id-offset ':' id-name ':' src ' ' type '\n'
311+ // id-offset: <int>
312+ // id-name: <string>
313+ id_table_t read_id_table(util::buffered_reader_t &r, type_table_t &tt, sbl_read_context_t &fc)
314+ {
315+ auto count_str = r.get_as_string_until('\n');
316+ int32_t count = std::stoi(count_str);
317+
318+ bool success;
319+ uint8_t tmp;
320+ id_table_t id_table;
321+ id_table.resize(count);
322+ for (auto i = 0; i < count; ++i)
323+ {
324+ const auto offset_part = r.get_as_string_until(':');
325+ if (offset_part.empty())
326+ throw std::runtime_error{ "Invalid ID table item (offset)" };
327+
328+ const auto name_part = r.get_as_string_until(':');
329+ if (name_part.empty())
330+ throw std::runtime_error{ "Invalid ID table item (name)" };
331+
332+ const auto source_part = r.get_as_string_until(' ');
333+ if (source_part.empty())
334+ throw std::runtime_error{ "Invalid ID table item (source)" };
335+
336+ id_item_t e;
337+ e.offset = std::stoul(offset_part);
338+ e.name = std::move(name_part);
339+ e.source = read_src_entry(source_part, fc);
340+ e.type = read_type(r, tt, fc);
341+
342+ std::tie(success, tmp) = r.get_next_byte();
343+ if (!success || tmp != '\n')
344+ throw std::runtime_error{ "Invalid ID table item" };
345+
346+ id_table[i] = std::move(e);
347+ }
348+
349+ return id_table;
350+ }
351+
352+ // adt-item: adt-name ' ' src ' ' size '\n' id-table
353+ // adt-name: <string>
354+ // size: <int>
355+ //
356+ // tag-table: <count> '\n' tag-item*
357+ // tag-item: tag-name ':' src ' ' size '\n' id-table
358+ // | tag-name ':' src '\n'
359+ // tag-name: <string>
360+ name_source_size_t create_name_source_size(std::vector<std::string> &parts, util::buffered_reader_t &r, type_table_t &tt, sbl_read_context_t &fc)
361+ {
362+ assert(parts.size() == 2 || parts.size() == 3);
363+ name_source_size_t item;
364+ item.name = std::move(parts[0]);
365+ item.source = read_src_entry(parts[1], fc);
366+
367+ if (parts.size() == 3)
368+ {
369+ item.size_in_bytes = std::strtol(parts[2].c_str(), nullptr, 10);
370+ if (item.size_in_bytes == 0 && errno != 0)
371+ throw std::runtime_error{ "Invalid adt size" };
372+
373+ item.id_table = read_id_table(r, tt, fc);
374+ }
375+
376+ return item;
377+ }
378+
379+ // type:
380+ // '@' type-index '\n' # Type table reference
381+ // 'a' adt-item # ADT
382+ // 'p' adt-item tag-table # ADT-Pick
383+ // 't' size '.' id-table # Tuple
384+ // 'm' module '\n' src # Module
385+ // 'F' fn-name ' ' type # Function
386+ // 'A' type # Array
387+ // 'C' type # Channel
388+ // 'L' type # List
389+ // 'R' type # Ref
390+ // 'n' # No type
391+ // 'N' # Nil
392+ // 'B' # Big
393+ // 'b' # Byte
394+ // 'i' # Int
395+ // 'f' # Real
396+ // 's' # String
397+ // type-index: <int>
398+ type_t read_type(util::buffered_reader_t &r, type_table_t &tt, sbl_read_context_t &fc)
399+ {
400+ bool success;
401+ uint8_t tc_byte;
402+ std::tie(success, tc_byte) = r.get_next_byte();
403+ if (!success)
404+ throw std::runtime_error{ "Invalid type" };
405+
406+ const type_class_t tc = static_cast<type_class_t>(tc_byte);
407+ type_t t{ tc, 0, 0 };
408+ switch (t.type_class)
409+ {
410+ case type_class_t::none:
411+ case type_class_t::nil:
412+ case type_class_t::big:
413+ case type_class_t::byte:
414+ case type_class_t::integer:
415+ case type_class_t::real:
416+ case type_class_t::string:
417+ case type_class_t::poly:
418+ break;
419+
420+ case type_class_t::array:
421+ case type_class_t::channel:
422+ case type_class_t::list:
423+ case type_class_t::ref:
424+ t.value_index_1 = type_table_t::inner_type_table_mask | tt.inner_type_table.size();
425+ tt.inner_type_table.push_back(read_type(r, tt, fc));
426+ break;
427+
428+ case type_class_t::type_index:
429+ {
430+ auto type_index = r.get_as_string_until('\n');
431+ const auto idx = std::stoi(type_index);
432+ if (idx == 0)
433+ throw std::runtime_error{ "Invalid type index" };
434+
435+ t.value_index_1 = type_table_t::types_mask | idx;
436+ break;
437+ }
438+ case type_class_t::adt:
439+ case type_class_t::adt_pick:
440+ {
441+ auto adt_line = r.get_as_string_until('\n');
442+ auto adt_parts = util::split(adt_line);
443+ if (adt_parts.size() != 3)
444+ throw std::runtime_error{ "Invalid adt item" };
445+
446+ auto adt_item = create_name_source_size(adt_parts, r, tt, fc);
447+
448+ // Check if the adt has a tag table
449+ if (t.type_class == type_class_t::adt_pick)
450+ {
451+ adt_item.tag_table = std::make_unique<std::vector<name_source_size_t>>();
452+ auto tmp = r.get_as_string_until('\n');
453+
454+ const auto count = std::stoi(tmp);
455+ for (auto i = 0; i < count; ++i)
456+ {
457+ tmp = r.get_as_string_until('\n');
458+ auto tag_parts = util::split(tmp, " :");
459+ if (tag_parts.size() != 2 && tag_parts.size() != 3)
460+ throw std::runtime_error{ "Invalid tag item" };
461+
462+ auto tag = create_name_source_size(tag_parts, r, tt, fc);
463+ adt_item.tag_table->push_back(std::move(tag));
464+ }
465+ }
466+
467+ t.value_index_1 = type_table_t::adt_table_mask | tt.adt_table.size();
468+ tt.adt_table.push_back(std::move(adt_item));
469+ break;
470+ }
471+ case type_class_t::tuple:
472+ {
473+ // Record tuple size
474+ t.value_index_1 = type_table_t::tuple_type_mask | tt.tuple_table.size();
475+
476+ tuple_item_t tuple{};
477+ auto size_str = r.get_as_string_until('.');
478+ tuple.size_in_bytes = std::stoi(size_str);
479+
480+ tuple.id_table = read_id_table(r, tt, fc);
481+ tt.tuple_table.push_back(std::move(tuple));
482+ break;
483+ }
484+ case type_class_t::module:
485+ {
486+ // Record module name
487+ t.value_index_1 = type_table_t::string_table_mask | tt.strings_table.size();
488+ tt.strings_table.push_back(r.get_as_string_until('\n'));
489+
490+ auto src_str = r.get_as_string_until('\n');
491+ t.value_index_2 = type_table_t::source_table_mask | tt.source_table.size();
492+ tt.source_table.push_back(read_src_entry(src_str, fc));
493+ break;
494+ }
495+
496+ case type_class_t::function:
497+ {
498+ // Record function name
499+ t.value_index_1 = type_table_t::string_table_mask | tt.strings_table.size();
500+ auto name = r.get_as_string_until(' ');
501+ if (name.empty())
502+ throw std::runtime_error{ "Invalid function type" };
503+
504+ tt.strings_table.push_back(std::move(name));
505+
506+ // Record return type
507+ t.value_index_2 = type_table_t::inner_type_table_mask | tt.inner_type_table.size();
508+ tt.inner_type_table.push_back(read_type(r, tt, fc));
509+ break;
510+ }
511+
512+ default:
513+ throw std::runtime_error{ "Invalid type value" };
514+ }
515+
516+ return t;
517+ }
518+
519+ // type-table: <count> '\n' type*
520+ type_table_t read_type_table(util::buffered_reader_t &r, sbl_read_context_t &fc)
521+ {
522+ auto count_str = r.get_as_string_until('\n');
523+
524+ type_table_t type_table;
525+ const auto count = std::stoi(count_str);
526+ type_table.types.resize(count);
527+ for (auto i = 0; i < count; ++i)
528+ type_table.types[i] = read_type(r, type_table, fc);
529+
530+ return type_table;
531+ }
532+
533+ // func-table: <count> '\n' fn-item*
534+ // fn-item: fn-pc ':' fn-name '\n' args locals return
535+ // fn-pc: <int>
536+ // fn-name: <string>
537+ // args: id-table
538+ // locals: id-table
539+ // return: type
540+ func_table_t read_func_table(util::buffered_reader_t &r, type_table_t &tt, sbl_read_context_t &fc)
541+ {
542+ auto count_str = r.get_as_string_until('\n');
543+
544+ const auto last_pc = fc.pc_count - 1;
545+ func_table_t func_table;
546+ const auto count = std::stoi(count_str);
547+ func_table.resize(count);
548+ for (auto i = 0; i < count; ++i)
549+ {
550+ func_item_t func;
551+ func.entry_pc = std::stoi(r.get_as_string_until(':'));
552+ func.limit_pc = last_pc;
553+ func.name = std::move(r.get_as_string_until('\n'));
554+ func.args = read_id_table(r, tt, fc);
555+ func.locals = read_id_table(r, tt, fc);
556+ func.return_type = read_type(r, tt, fc);
557+
558+ // Update the previous functions limit
559+ if (i > 0)
560+ {
561+ assert(func_table[i - 1].entry_pc < func.entry_pc && "Function table in sbl file is assumed to be in pc order");
562+ func_table[i - 1].limit_pc = func.entry_pc - 1;
563+ }
564+
565+ func_table[i] = std::move(func);
566+ }
567+
568+ return func_table;
569+ }
570+
571+ // data-table: id-table
572+ id_table_t read_data_table(util::buffered_reader_t &r, type_table_t &tt, sbl_read_context_t &fc)
573+ {
574+ return read_id_table(r, tt, fc);
575+ }
576+}
577+
578+namespace
579+{
580+ class sbl_symbol_debug final : public symbol_debug_t
581+ {
582+ public:
583+ sbl_symbol_debug(std::shared_ptr<sbl_file_t> f)
584+ : _current_pc{ 0 }
585+ , _file{ f }
586+ {
587+ assert(_file != nullptr);
588+ }
589+
590+ public: // symbol_debug_t
591+ void set_current_pc(disvm::runtime::vm_pc_t pc) override
592+ {
593+ assert(0 <= pc && pc < static_cast<int32_t>(_file->pc_table.size()));
594+ _current_pc = pc;
595+ }
596+
597+ bool try_advance_pc(advance_pc_t adv, disvm::runtime::vm_pc_t *pc_after_advance) override
598+ {
599+ runtime::vm_pc_t new_pc = _current_pc;
600+ switch (adv)
601+ {
602+ case advance_pc_t::next_debug_statement:
603+ {
604+ auto iter = std::next(std::cbegin(_file->pc_table), new_pc);
605+ const auto current_dbg = iter->debug_statement;
606+
607+ // Iterate until the next pc entry that is not the same debug statement or the end.
608+ while (iter != std::cend(_file->pc_table) && iter->debug_statement == current_dbg)
609+ {
610+ ++iter;
611+ }
612+
613+ if (iter == std::cend(_file->pc_table))
614+ return false;
615+
616+ new_pc = std::distance(std::cbegin(_file->pc_table), iter);
617+ break;
618+ }
619+
620+ case advance_pc_t::next_pc:
621+ if (new_pc + 1 >= static_cast<runtime::vm_pc_t>(_file->pc_table.size()))
622+ return false;
623+ ++new_pc;
624+ break;
625+
626+ case advance_pc_t::current:
627+ assert(pc_after_advance != nullptr);
628+ break;
629+
630+ default:
631+ throw std::runtime_error{ "Invalid advance value" };
632+ }
633+
634+ if (pc_after_advance != nullptr)
635+ *pc_after_advance = new_pc;
636+
637+ _current_pc = new_pc;
638+ return true;
639+ }
640+
641+ const std::string &current_function_name() const override
642+ {
643+ for (auto &e : _file->func_table)
644+ {
645+ if (e.entry_pc <= _current_pc && _current_pc <= e.limit_pc)
646+ return e.name;
647+ }
648+
649+ throw std::runtime_error{ "Unknown function" };
650+ }
651+
652+ source_ref_t current_source_location() const override
653+ {
654+ assert(0 <= _current_pc && _current_pc < static_cast<int32_t>(_file->pc_table.size()));
655+ return _file->pc_table[_current_pc].source;
656+ }
657+
658+ const std::string &get_source_by_id(int32_t source_id) const override
659+ {
660+ assert(0 <= source_id && source_id < static_cast<int32_t>(_file->file_table.size()));
661+ return _file->file_table[source_id];
662+ }
663+
664+ private:
665+ runtime::vm_pc_t _current_pc;
666+ std::shared_ptr<sbl_file_t> _file;
667+ };
668+
669+ class sbl_symbol_data final : public symbol_data_t
670+ {
671+ public:
672+ sbl_symbol_data(std::shared_ptr<sbl_file_t> f)
673+ : _file{ f }
674+ {
675+ assert(_file != nullptr);
676+ }
677+
678+ public: // symbol_data_t
679+ const std::string & get_module_name() const override
680+ {
681+ return _file->header.module_name;
682+ }
683+
684+ std::unique_ptr<symbol_debug_t> get_debug() override
685+ {
686+ return std::make_unique<sbl_symbol_debug>(_file);
687+ }
688+
689+ private:
690+ std::shared_ptr<sbl_file_t> _file;
691+ };
692+}
693+
694+std::unique_ptr<symbol_data_t> disvm::symbol::read(std::istream &f)
695+{
696+ auto file = std::make_shared<sbl_file_t>();
697+
698+ util::buffered_reader_t reader{ f };
699+
700+ file->header = read_header(reader);
701+ file->file_table = read_file_table(reader);
702+
703+ sbl_read_context_t fc{};
704+ file->pc_table = read_pc_table(reader, fc);
705+ file->type_table = read_type_table(reader, fc);
706+ file->func_table = read_func_table(reader, file->type_table, fc);
707+ file->data_table = read_data_table(reader, file->type_table, fc);
708+
709+ return std::make_unique<sbl_symbol_data>(file);
710+}