[Decompiler] Experimental Expression Stack (#157)

* begin framework for expressions

* more

* clean up warnings

* small fixes

* update

* wip type prop improvements

* see if nasm works

* fix format strings
This commit is contained in:
water111
2020-12-17 15:48:07 -05:00
committed by GitHub
parent ba919a069c
commit cae3871730
38 changed files with 2064 additions and 631 deletions
+7 -5
View File
@@ -586,7 +586,7 @@ std::string LinkedObjectFile::print_function_disassembly(Function& func,
result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n";
result += func.prologue.to_string(2) + "\n";
if (!func.warnings.empty()) {
result += "Warnings: " + func.warnings + "\n";
result += ";;Warnings:\n" + func.warnings + "\n";
}
// print each instruction in the function.
@@ -784,7 +784,7 @@ std::string LinkedObjectFile::print_type_analysis_debug() {
result += "; .function " + func.guessed_name.to_string() + "\n";
result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n";
if (!func.warnings.empty()) {
result += ";; WARNING: " + func.warnings + "\n";
result += ";; WARNING:\n" + func.warnings + "\n";
}
for (auto& block : func.basic_blocks) {
@@ -799,10 +799,12 @@ std::string LinkedObjectFile::print_type_analysis_debug() {
// result += func.basic_ops.at(i)->print_with_reguse(*this);
// result += func.basic_ops.at(i)->print(*this);
if (func.attempted_type_analysis) {
result += fmt::format("[{:3d}] ", i);
result += func.basic_ops.at(i)->print_with_types(*init_types, *this);
result += "\n";
init_types = &func.basic_ops.at(i)->end_types;
} else {
result += fmt::format("[{:3d}] ", i);
result += func.basic_ops.at(i)->print(*this);
result += "\n";
}
@@ -817,7 +819,7 @@ std::string LinkedObjectFile::print_type_analysis_debug() {
/*!
* Hacky way to get a GOAL string object
*/
std::string LinkedObjectFile::get_goal_string(int seg, int word_idx, bool with_quotes) {
std::string LinkedObjectFile::get_goal_string(int seg, int word_idx, bool with_quotes) const {
std::string result;
if (with_quotes) {
result += "\"";
@@ -826,7 +828,7 @@ std::string LinkedObjectFile::get_goal_string(int seg, int word_idx, bool with_q
if (word_idx + 1 >= int(words_by_seg[seg].size())) {
return "invalid string!\n";
}
LinkedWord& size_word = words_by_seg[seg].at(word_idx + 1);
const LinkedWord& size_word = words_by_seg[seg].at(word_idx + 1);
if (size_word.kind != LinkedWord::PLAIN_DATA) {
// sometimes an array of string pointer triggers this!
return "invalid string!\n";
@@ -1036,7 +1038,7 @@ u32 LinkedObjectFile::read_data_word(const Label& label) {
return word.data;
}
std::string LinkedObjectFile::get_goal_string_by_label(const Label& label) {
std::string LinkedObjectFile::get_goal_string_by_label(const Label& label) const {
assert(0 == (label.offset % 4));
return get_goal_string(label.target_segment, (label.offset / 4) - 1, false);
}
+2 -2
View File
@@ -70,7 +70,7 @@ class LinkedObjectFile {
std::string print_asm_function_disassembly(const std::string& my_name);
u32 read_data_word(const Label& label);
std::string get_goal_string_by_label(const Label& label);
std::string get_goal_string_by_label(const Label& label) const;
struct Stats {
uint32_t total_code_bytes = 0;
@@ -138,7 +138,7 @@ class LinkedObjectFile {
goos::Object to_form_script_object(int seg, int byte_idx, std::vector<bool>& seen);
bool is_empty_list(int seg, int byte_idx);
bool is_string(int seg, int byte_idx);
std::string get_goal_string(int seg, int word_idx, bool with_quotes = true);
std::string get_goal_string(int seg, int word_idx, bool with_quotes = true) const;
std::vector<std::unordered_map<int, int>> label_per_seg_by_offset;
};
+81 -40
View File
@@ -560,7 +560,8 @@ void ObjectFileDB::write_object_file_words(const std::string& output_dir, bool d
// printf("\n");
}
void ObjectFileDB::write_debug_type_analysis(const std::string& output_dir) {
void ObjectFileDB::write_debug_type_analysis(const std::string& output_dir,
const std::string& suffix) {
spdlog::info("- Writing debug type analysis...");
Timer timer;
uint32_t total_bytes = 0, total_files = 0;
@@ -568,7 +569,8 @@ void ObjectFileDB::write_debug_type_analysis(const std::string& output_dir) {
for_each_obj([&](ObjectFileData& obj) {
if (obj.linked_data.has_any_functions()) {
auto file_text = obj.linked_data.print_type_analysis_debug();
auto file_name = file_util::combine_path(output_dir, obj.to_unique_name() + "_db.asm");
auto file_name =
file_util::combine_path(output_dir, obj.to_unique_name() + suffix + "_db.asm");
total_bytes += file_text.size();
file_util::write_text_file(file_name, file_text);
@@ -588,7 +590,8 @@ void ObjectFileDB::write_debug_type_analysis(const std::string& output_dir) {
*/
void ObjectFileDB::write_disassembly(const std::string& output_dir,
bool disassemble_objects_without_functions,
bool write_json) {
bool write_json,
const std::string& file_suffix) {
spdlog::info("- Writing functions...");
Timer timer;
uint32_t total_bytes = 0, total_files = 0;
@@ -599,7 +602,8 @@ void ObjectFileDB::write_disassembly(const std::string& output_dir,
if (obj.linked_data.has_any_functions() || disassemble_objects_without_functions) {
auto file_text = obj.linked_data.print_disassembly();
asm_functions += obj.linked_data.print_asm_function_disassembly(obj.to_unique_name());
auto file_name = file_util::combine_path(output_dir, obj.to_unique_name() + ".asm");
auto file_name =
file_util::combine_path(output_dir, obj.to_unique_name() + file_suffix + ".asm");
if (get_config().analyze_functions && write_json) {
auto json_asm_text = obj.linked_data.to_asm_json(obj.to_unique_name());
@@ -811,7 +815,7 @@ void ObjectFileDB::analyze_functions() {
unique_names.insert(name);
if (config.asm_functions_by_name.find(name) != config.asm_functions_by_name.end()) {
func.warnings += "flagged as asm by config\n";
func.warnings += ";; flagged as asm by config\n";
func.suspected_asm = true;
}
}
@@ -824,7 +828,7 @@ void ObjectFileDB::analyze_functions() {
if (duplicated_functions.find(name) != duplicated_functions.end()) {
duplicated_functions[name].insert(data.to_unique_name());
func.warnings += "this function exists in multiple non-identical object files";
func.warnings += ";; this function exists in multiple non-identical object files";
}
});
/*
@@ -917,54 +921,66 @@ void ObjectFileDB::analyze_functions() {
// type analysis
if (get_config().function_type_prop) {
if (func.guessed_name.kind == FunctionName::FunctionKind::GLOBAL) {
// we're a global named function. This means we're stored in a symbol
auto kv = dts.symbol_types.find(func.guessed_name.function_name);
if (kv != dts.symbol_types.end() && kv->second.arg_count() >= 1) {
if (kv->second.base_type() != "function") {
spdlog::error("Found a function named {} but the symbol has type {}",
func.guessed_name.to_string(), kv->second.print());
assert(false);
}
// GOOD!
func.type = kv->second;
func.attempted_type_analysis = true;
attempted_type_analysis++;
spdlog::info("Type Analysis on {} {}", func.guessed_name.to_string(),
kv->second.print());
if (func.run_type_analysis(kv->second, dts, data.linked_data)) {
successful_type_analysis++;
}
}
} else if (func.guessed_name.kind == FunctionName::FunctionKind::METHOD) {
// it's a method.
try {
auto info =
dts.ts.lookup_method(func.guessed_name.type_name, func.guessed_name.method_id);
if (info.type.arg_count() >= 1) {
if (info.type.base_type() != "function") {
spdlog::error("Found a method named {} but the symbol has type {}",
func.guessed_name.to_string(), info.type.print());
auto hints = get_config().type_hints_by_function_by_idx[func.guessed_name.to_string()];
if (get_config().no_type_analysis_functions_by_name.find(func.guessed_name.to_string()) ==
get_config().no_type_analysis_functions_by_name.end()) {
if (func.guessed_name.kind == FunctionName::FunctionKind::GLOBAL) {
// we're a global named function. This means we're stored in a symbol
auto kv = dts.symbol_types.find(func.guessed_name.function_name);
if (kv != dts.symbol_types.end() && kv->second.arg_count() >= 1) {
if (kv->second.base_type() != "function") {
spdlog::error("Found a function named {} but the symbol has type {}",
func.guessed_name.to_string(), kv->second.print());
assert(false);
}
// GOOD!
func.type = info.type.substitute_for_method_call(func.guessed_name.type_name);
func.type = kv->second;
func.attempted_type_analysis = true;
attempted_type_analysis++;
spdlog::info("Type Analysis on {} {}", func.guessed_name.to_string(),
func.type.print());
if (func.run_type_analysis(func.type, dts, data.linked_data)) {
// spdlog::info("Type Analysis on {} {}", func.guessed_name.to_string(),
// kv->second.print());
if (func.run_type_analysis(kv->second, dts, data.linked_data, hints)) {
successful_type_analysis++;
}
}
} else if (func.guessed_name.kind == FunctionName::FunctionKind::METHOD) {
// it's a method.
try {
auto info =
dts.ts.lookup_method(func.guessed_name.type_name, func.guessed_name.method_id);
if (info.type.arg_count() >= 1) {
if (info.type.base_type() != "function") {
spdlog::error("Found a method named {} but the symbol has type {}",
func.guessed_name.to_string(), info.type.print());
assert(false);
}
// GOOD!
func.type = info.type.substitute_for_method_call(func.guessed_name.type_name);
func.attempted_type_analysis = true;
attempted_type_analysis++;
// spdlog::info("Type Analysis on {} {}",
// func.guessed_name.to_string(),
// func.type.print());
if (func.run_type_analysis(func.type, dts, data.linked_data, hints)) {
successful_type_analysis++;
}
}
} catch (std::runtime_error& e) {
// failed to lookup method info
} catch (std::runtime_error& e) {
// failed to lookup method info
}
}
if (!func.attempted_type_analysis) {
func.warnings.append(";; Failed to try type analysis\n");
}
} else {
func.warnings.append(";; Marked as no type analysis in config\n");
}
}
} else {
asm_funcs++;
func.warnings.append(";; Assembly Function. Analysis passes were not attempted.\n");
}
if (func.basic_blocks.size() > 1 && !func.suspected_asm) {
@@ -982,6 +998,10 @@ void ObjectFileDB::analyze_functions() {
if (!func.guessed_name.empty()) {
total_named_functions++;
}
// if (func.guessed_name.to_string() == "reset-and-call") {
// assert(false);
// }
});
spdlog::info("Found {} functions ({} with no control flow)", total_functions,
@@ -1015,6 +1035,27 @@ void ObjectFileDB::analyze_functions() {
// }
}
void ObjectFileDB::analyze_expressions() {
spdlog::info("- Analyzing Expressions...");
Timer timer;
int attempts = 0;
int success = 0;
for_each_function_def_order([&](Function& func, int segment_id, ObjectFileData& data) {
(void)segment_id;
// register usage
func.run_reg_usage();
attempts++;
if (func.build_expression(data.linked_data)) {
success++;
} else {
func.warnings.append(";; Expression analysis failed.\n");
}
});
spdlog::info(" {}/{} functions passed expression building ({:.2f}%)\n", success, attempts,
100.f * float(success) / float(attempts));
}
void ObjectFileDB::dump_raw_objects(const std::string& output_dir) {
for_each_obj([&](ObjectFileData& data) {
auto dest = output_dir + "/" + data.to_unique_name();
+4 -2
View File
@@ -60,11 +60,13 @@ class ObjectFileDB {
void write_object_file_words(const std::string& output_dir, bool dump_v3_only);
void write_disassembly(const std::string& output_dir,
bool disassemble_objects_without_functions,
bool write_json);
bool write_json,
const std::string& file_suffix = "");
void write_debug_type_analysis(const std::string& output_dir);
void write_debug_type_analysis(const std::string& output_dir, const std::string& suffix = "");
void analyze_functions();
void process_tpages();
void analyze_expressions();
std::string process_game_count();
std::string process_game_text();