如何在本地生成 godbolt 之类的 clean assembly?

How to generate godbolt like clean assembly locally?

我想在尝试之前生成像 Compiler Explorer locally. Note that, I read 这样的干净程序集。与 Godbolt 相比,使用该方法的输出没有那么干净或密集,并且其中仍然有很多 asm 指令和未使用的标签。

如何在没有任何未使用的标签或指令的情况下获得干净的汇编输出

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

它尝试 'clean' 并使用 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 .
  • 的回答
  • 该程序可以独立运行,但我强烈建议阅读 SO answer 以调整您的 asm 输出,然后使用该程序处理它以删除未使用的标签/指令等。
  • abi::__cxa_demangle()用于拆包
  • 免责声明:这不是一个完美的解决方案,也没有经过广泛测试。

用于清理 asm 的策略(可能有更好、更快、更有效的方法来执行此操作):

  1. 收集所有标签
  2. 逐行检查 asm 并检查标签是否 used/unused
  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;
}

郑重声明,可以(而且显然不太难)在本地安装 Matt Godbolt 的编译器资源管理器 ,因此您可以使用它来探索 asm属于现有大型项目的文件的输出及其 #include 依赖项和所有内容。

如果您已经有一些 asm 输出,@Waqar 的回答看起来很有用。或者也许可以通过 node.js、IDK.

从 Compiler Explorer 存储库中单独使用该功能

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


我还找到了 https://isocpp.org/blog/2017/10/cpp-weekly-episode-83-installing-compiler-explorerjason-turner,其中可能包含更多详细信息(或者在这一点上已过时,IDK)。

我认为 Matt 在他的 CppCon 2017 中关于 Compiler Explorer 的演讲中也提到了使用 Compiler Explorer 的本地克隆(可能在最后回答了一个问题),“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”, and recommends it for playing with code that uses lots of #include that would be hard to get onto https://godbolt.org/。 (或者对于闭源代码)。

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

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