【问题标题】:How to generate godbolt like clean assembly locally?如何在本地生成像清洁组件一样的godbolt?
【发布时间】:2020-11-10 22:05:36
【问题描述】:

我想在本地生成像Compiler Explorer 这样的干净程序集。请注意,我在尝试此操作之前阅读了How to remove “noise” from GCC/clang assembly output?。与 Godbolt 相比,使用该方法的输出没有那么干净或密集,并且仍然有很多 asm 指令和未使用的标签。

我怎样才能获得干净的汇编输出没有任何未使用的标签或指令

【问题讨论】:

  • 您是否考虑过克隆编译器资源库并在本地使用它? Matt Godbolt 建议使用 asm 输出文件,该文件是具有大量依赖项的大型项目的一部分。例如isocpp.org/blog/2017/10/… 显然解释了如何。我认为 Matt 在他关于 Compiler Explorer 的 CPPcon 演讲中也提到了这一点,“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”
  • @PeterCordes 我确实尝试过,但出于几个原因我这样做了。 A)为此在本地设置godbolt并非易事。 B) Godbolt 不能清理已经生成的程序集。 C) 我只是想自己做这件事,看看编译器在幕后实际做了什么。
  • 使用不需要的 objdump 和过滤标签简单地转储 elf 文件的内容。

标签: c++ assembly gcc clang compiler-explorer


【解决方案1】:

作为记录,可以(显然不是太难)设置 Matt Godbolt 的 Compiler Explorer 的本地安装,因此您可以使用它来探索 asm 输出的文件现有大型项目的一部分及其 #include 依赖项和所有内容。

如果您已经有一些 asm 输出,@Waqar 的答案看起来很有用。或者,也许可以通过 node.js、IDK 从 Compiler Explorer 存储库中单独使用该功能。

根据https://github.com/compiler-explorer/compiler-explorer(Matt 的仓库)中的自述文件中的安装信息,您可以在安装了 node.js 的机器上克隆它后简单地运行make


我还发现了https://isocpp.org/blog/2017/10/cpp-weekly-episode-83-installing-compiler-explorerjason-turner,它可能有更多细节(或者在这一点上已经过时,IDK)。

我认为 Matt 在他的 CppCon 2017 演讲中提到了使用 Compiler Explorer 的本地克隆(可能会在最后回答一个问题)“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”,并建议使用它来处理使用大量代码的代码的#include 很难进入https://godbolt.org/。 (或者对于封闭源代码)。

【讨论】:

    【解决方案2】:

    不久前,我在本地需要这样的东西,所以我写了一个小工具来使 asm 可读。

    它尝试“清理”并使用 C++ 本身使“gcc”的“asm”输出可读。它执行类似于Compiler Explorer 的操作,并尝试删除所有指令和未使用的标签,从而使 asm 变得干净。仅使用标准库。

    我应该提到一些事情:

    • 仅适用于 gcc 和 clang
    • 仅使用 C++ 代码测试
    • -S -fno-asynchronous-unwind-tables -fno-dwarf2-cfi-asm -masm=intel编译,(如果你想要AT&T asm,去掉-masm=) AT&T 语法可能会起作用,但我没有对其进行太多测试。其他两个选项是删除 .cfi 指令。它可以使用下面的代码来处理,但编译器本身在这方面做得更好。请参阅 Peter Cordes above 的答案。
    • 此程序可以独立运行,但我强烈建议阅读this SO 答案以调整您的 asm 输出,然后使用此程序对其进行处理以删除未使用的标签/指令等。
    • abi::__cxa_demangle() 用于拆解
    • 免责声明:这不是一个完美的解决方案,也没有经过广泛的测试。

    用于清理 asm 的策略(可能有更好、更快、更有效的方法):

    1. 收集所有标签
    2. 逐行检查 asm 并检查标签是否已使用/未使用
    3. 如果标签未被使用,它们将被删除
    4. 以“.”开头的每一行被删除,除非它在某处被使用过

    更新 1:现在并非所有静态数据都被删除。

    #include <algorithm>
    #include <cxxabi.h>
    #include <fstream>
    #include <iostream>
    #include <regex>
    #include <string>
    #include <sstream>
    #include <unordered_map>
    
    // trim from both ends (in place)
    std::string_view trim(std::string_view s)
    {
        s.remove_prefix(std::min(s.find_first_not_of(" \t\r\v\n"), s.size()));
        s.remove_suffix(std::min(s.size() - s.find_last_not_of(" \t\r\v\n") - 1, s.size()));
        return s;
    }
    
    static inline bool startsWith(const std::string_view s, const std::string_view searchString)
    {
        return (s.rfind(searchString, 0) == 0);
    }
    
    std::string demangle(std::string &&asmText)
    {
        int next = 0;
        int last = 0;
        while (next != -1) {
            next = asmText.find("_Z", last);
            //get token
            if (next != -1) {
                int tokenEnd = asmText.find_first_of(":,.@[]() \n", next + 1);
                int len = tokenEnd - next;
                std::string tok = asmText.substr(next, len);
                int status = 0;
                char* name = abi::__cxa_demangle(tok.c_str(), 0, 0, &status);
                if (status != 0) {
                    std::cout << "Demangling of: " << tok << " failed, status: " << status << '\n';
                    continue;
                }
                std::string demangledName{name};
                demangledName.insert(demangledName.begin(), ' ');
                asmText.replace(next, len, demangledName);
                free((void*)name);
            }
        }
        return std::move(asmText);
    }
    
    std::string clean_asm(const std::string& asmText)
    {
        std::string output;
        output.reserve(asmText.length());
        std::stringstream s{asmText};
    
        //1. collect all the labels
        //2. go through the asm line by line and check if the labels are used/unused
        //3. if the labels are unused, they get deleted
        //4. every line beginning with '.' gets deleted, unless it is a used label
    
        std::regex exp {"^\\s*[_|a-zA-Z]"};
        
        std::regex directiveRe { "^\\s*\\..*$" };
        std::regex labelRe { "^\\.*[a-zA-Z]+[0-9]+:$" };
        std::regex hasOpcodeRe { "^\\s*[a-zA-Z]" };
        std::regex numericLabelsRe { "\\s*[0-9]:" };
    
        const std::vector<std::string> allowedDirectives =
        {
            ".string", ".zero", ".byte", ".value", ".long", ".quad", ".ascii"
        };
    
        //<label, used>
        std::unordered_map<std::string, bool> labels;
    
        //1
        std::string line;
        while (std::getline(s, line)) {
            if (std::regex_match(line, labelRe)) {
                trim(line);
                // remove ':'
                line = line.substr(0, line.size() - 1);
                labels[line] = false;
            }
        }
    
        s.clear();
        s.str(asmText);
        line = "";
    
        //2
        while (std::getline(s, line)) {
            if (std::regex_match(line, hasOpcodeRe)) {
                auto it = labels.begin();   
                for (; it != labels.end(); ++it) {
                    if (line.find(it->first)) {
                        labels[it->first] = true;
                    }
                }
            }
        }
    
        //remove false labels from labels hash-map
        for (auto it = labels.begin(); it != labels.end();) {
            if (it->second == false)
                it = labels.erase(it);
            else
                ++it;
        }
    
        s.clear();
        s.str(asmText);
        line = "";
    
        std::string currentLabel;
    
        //3
        while (std::getline(s, line)) {
            trim(line);
    
            if (std::regex_match(line, labelRe)) {
                auto l = line;
                l = l.substr(0, l.size() - 1);
                currentLabel = "";
                if (labels.find(l) != labels.end()) {
                    currentLabel = line;
                    output += line + "\n";
                }
                continue;
            }
    
            if (std::regex_match(line, directiveRe)) {
                //if we are in a label
                if (!currentLabel.empty()) {
                    auto trimmedLine = trim(line);
                    for (const auto& allowedDir : allowedDirectives) {
                        if (startsWith(trimmedLine, allowedDir)) {
                            output += line;
                            output += '\n';
                        }
                    }
                }
                continue;
            }
    
            if (std::regex_match(line, numericLabelsRe)) {
                continue;
            }
    
            if (line == "endbr64") {
                continue;
            }
    
            if (line[line.size() - 1] == ':' || line.find(':') != std::string::npos) {
                currentLabel = line;
                output += line + '\n';
                continue;
            }
    
            line.insert(line.begin(), '\t');
    
            output += line + '\n';
        }
    
        return output;
    }
    
    int main(int argc, char* argv[])
    {
        if (argc < 2) {
            std::cout << "Please provide more than asm filename you want to process.\n";
        }
        std::ifstream file(argv[1]);
        std::string output;
        if (file.is_open()) {
            std::cout << "File '" << argv[1] << "' is opened\n";
            std::string line;
            while (std::getline(file, line)) {
                output += line + '\n';
            }
        }
    
        output = demangle(std::move(output));
        output = clean_asm(output);
    
        std::string fileName = argv[1];
        auto dotPos = fileName.rfind('.');
        if (dotPos != std::string::npos)
            fileName.erase(fileName.begin() + dotPos, fileName.end());
    
        std::cout << "Asm processed. Saving as '"<< fileName <<".asm'";
        std::ofstream out;
        out.open(fileName + ".asm");
        out << output;
    
        return 0;
    }
    

    【讨论】:

    • 删除每个. 行将删除像.byte.ascii 这样的指令。有时您确实想查看编译器发出的静态数据/常量。 (但这更难,所以第一步只是把它全部杀掉就可以了。)
    • 更新:它现在不会删除所有指令,因此使用的指令仍将存在于输出中。
    【解决方案3】:

    我检查了编译器资源管理器,看看他们是否有一组特定的编译器选项来获取他们的输出。但他们没有。相反,他们使用this function 过滤程序集列表。还有一个额外的处理步骤,将调试信息合并到源代码和程序集突出显示中。

    为了回答您的问题,我认为现在(2021 年 9 月)GCC 本身不可能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-14
      • 1970-01-01
      • 1970-01-01
      • 2017-01-15
      • 2015-04-26
      • 1970-01-01
      • 2020-03-28
      • 1970-01-01
      相关资源
      最近更新 更多