[tools][wasm] Add "wami", the Wasm Module Inspector
Initial feature: list functions in a module, as follows: $ gm x64.release wami $ out/x64.release/wami --list-functions my_module.wasm More to come. Change-Id: I9580437d51153e1b5ccc291fdb6a6a67315be07d Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3742700 Commit-Queue: Jakob Kummerow <jkummerow@chromium.org> Reviewed-by: Manos Koukoutos <manoskouk@chromium.org> Cr-Commit-Position: refs/heads/main@{#81515}
This commit is contained in:
parent
1c8d8d6c6c
commit
7f0c7fb074
@ -35,7 +35,7 @@ constexpr char kBranchHintsString[] = "metadata.code.branch_hint";
|
||||
constexpr char kDebugInfoString[] = ".debug_info";
|
||||
constexpr char kExternalDebugInfoString[] = "external_debug_info";
|
||||
|
||||
const char* ExternalKindName(ImportExportKindCode kind) {
|
||||
inline const char* ExternalKindName(ImportExportKindCode kind) {
|
||||
switch (kind) {
|
||||
case kExternalFunction:
|
||||
return "function";
|
||||
@ -51,7 +51,7 @@ const char* ExternalKindName(ImportExportKindCode kind) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const char* SectionName(SectionCode code) {
|
||||
inline const char* SectionName(SectionCode code) {
|
||||
switch (code) {
|
||||
case kUnknownSectionCode:
|
||||
return "Unknown";
|
||||
@ -100,7 +100,7 @@ const char* SectionName(SectionCode code) {
|
||||
}
|
||||
}
|
||||
|
||||
bool validate_utf8(Decoder* decoder, WireBytesRef string) {
|
||||
inline bool validate_utf8(Decoder* decoder, WireBytesRef string) {
|
||||
return unibrow::Utf8::ValidateEncoding(
|
||||
decoder->start() + decoder->GetBufferRelativeOffset(string.offset()),
|
||||
string.length());
|
||||
@ -110,8 +110,9 @@ enum class StringValidation { kNone, kUtf8, kWtf8 };
|
||||
|
||||
// Reads a length-prefixed string, checking that it is within bounds. Returns
|
||||
// the offset of the string, and the length as an out parameter.
|
||||
WireBytesRef consume_string(Decoder* decoder, StringValidation validation,
|
||||
const char* name) {
|
||||
inline WireBytesRef consume_string(Decoder* decoder,
|
||||
StringValidation validation,
|
||||
const char* name) {
|
||||
uint32_t length = decoder->consume_u32v("string length");
|
||||
uint32_t offset = decoder->pc_offset();
|
||||
const byte* string_start = decoder->pc();
|
||||
@ -138,11 +139,11 @@ WireBytesRef consume_string(Decoder* decoder, StringValidation validation,
|
||||
return {offset, decoder->failed() ? 0 : length};
|
||||
}
|
||||
|
||||
WireBytesRef consume_utf8_string(Decoder* decoder, const char* name) {
|
||||
inline WireBytesRef consume_utf8_string(Decoder* decoder, const char* name) {
|
||||
return consume_string(decoder, StringValidation::kUtf8, name);
|
||||
}
|
||||
|
||||
SectionCode IdentifyUnknownSectionInternal(Decoder* decoder) {
|
||||
inline SectionCode IdentifyUnknownSectionInternal(Decoder* decoder) {
|
||||
WireBytesRef string = consume_utf8_string(decoder, "section name");
|
||||
if (decoder->failed()) {
|
||||
return kUnknownSectionCode;
|
||||
|
@ -76,6 +76,15 @@ ModuleResult DecodeWasmModule(
|
||||
return result;
|
||||
}
|
||||
|
||||
ModuleResult DecodeWasmModuleForDisassembler(const byte* module_start,
|
||||
const byte* module_end,
|
||||
AccountingAllocator* allocator) {
|
||||
constexpr bool verify_functions = false;
|
||||
ModuleDecoderImpl decoder(WasmFeatures::All(), module_start, module_end,
|
||||
kWasmOrigin);
|
||||
return decoder.DecodeModule(nullptr, allocator, verify_functions);
|
||||
}
|
||||
|
||||
ModuleDecoder::ModuleDecoder(const WasmFeatures& enabled)
|
||||
: enabled_features_(enabled) {}
|
||||
|
||||
|
@ -91,6 +91,10 @@ V8_EXPORT_PRIVATE ModuleResult DecodeWasmModule(
|
||||
Counters* counters, std::shared_ptr<metrics::Recorder> metrics_recorder,
|
||||
v8::metrics::Recorder::ContextId context_id, DecodingMethod decoding_method,
|
||||
AccountingAllocator* allocator);
|
||||
// Stripped down version for disassembler needs.
|
||||
V8_EXPORT_PRIVATE ModuleResult DecodeWasmModuleForDisassembler(
|
||||
const byte* module_start, const byte* module_end,
|
||||
AccountingAllocator* allocator);
|
||||
|
||||
// Exposed for testing. Decodes a single function signature, allocating it
|
||||
// in the given zone. Returns {nullptr} upon failure.
|
||||
|
@ -22,7 +22,7 @@ namespace wasm {
|
||||
class DecodedNameSection;
|
||||
class StringBuilder;
|
||||
|
||||
class NamesProvider {
|
||||
class V8_EXPORT_PRIVATE NamesProvider {
|
||||
public:
|
||||
// {kWasmInternal}: only return raw name from name section.
|
||||
// {kDevTools}: prepend '$', use import/export names as fallback,
|
||||
|
@ -18,6 +18,10 @@ group("gn_all") {
|
||||
if (is_win) {
|
||||
data_deps += [ "v8windbg" ]
|
||||
}
|
||||
|
||||
if (v8_enable_webassembly) {
|
||||
data_deps += [ "wasm:wami" ]
|
||||
}
|
||||
}
|
||||
|
||||
group("v8_check_static_initializers") {
|
||||
|
@ -56,9 +56,11 @@ MODES = {
|
||||
# Modes that get built/run when you don't specify any.
|
||||
DEFAULT_MODES = ["release", "debug"]
|
||||
# Build targets that can be manually specified.
|
||||
TARGETS = ["d8", "cctest", "unittests", "v8_fuzzers", "wasm_api_tests", "wee8",
|
||||
"mkgrokdump", "generate-bytecode-expectations", "inspector-test",
|
||||
"bigint_shell"]
|
||||
TARGETS = [
|
||||
"d8", "cctest", "unittests", "v8_fuzzers", "wasm_api_tests", "wee8",
|
||||
"mkgrokdump", "generate-bytecode-expectations", "inspector-test",
|
||||
"bigint_shell", "wami"
|
||||
]
|
||||
# Build targets that get built when you don't specify any (and specified tests
|
||||
# don't imply any other targets).
|
||||
DEFAULT_TARGETS = ["d8"]
|
||||
|
36
tools/wasm/BUILD.gn
Normal file
36
tools/wasm/BUILD.gn
Normal file
@ -0,0 +1,36 @@
|
||||
# Copyright 2022 the V8 project authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import("../../gni/v8.gni")
|
||||
|
||||
config("internal_config") {
|
||||
visibility = [ ":*" ] # Only targets in this file can depend on this.
|
||||
|
||||
include_dirs = [
|
||||
".",
|
||||
"../..",
|
||||
"../../include/",
|
||||
"$target_gen_dir/../..",
|
||||
]
|
||||
}
|
||||
|
||||
# Wasm Module Inspector
|
||||
v8_executable("wami") {
|
||||
testonly = true
|
||||
|
||||
deps = [
|
||||
"../..:v8",
|
||||
"../..:v8_libbase",
|
||||
"../..:v8_libplatform",
|
||||
]
|
||||
|
||||
sources = [ "module-inspector.cc" ]
|
||||
|
||||
defines = []
|
||||
|
||||
configs = [ ":internal_config" ]
|
||||
if (v8_enable_i18n_support) {
|
||||
configs += [ "//third_party/icu:icu_config" ]
|
||||
}
|
||||
}
|
165
tools/wasm/module-inspector.cc
Normal file
165
tools/wasm/module-inspector.cc
Normal file
@ -0,0 +1,165 @@
|
||||
// Copyright 2022 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "include/libplatform/libplatform.h"
|
||||
#include "include/v8-initialization.h"
|
||||
#include "src/wasm/module-decoder-impl.h"
|
||||
#include "src/wasm/names-provider.h"
|
||||
#include "src/wasm/string-builder.h"
|
||||
#include "src/wasm/wasm-opcodes-inl.h"
|
||||
|
||||
int PrintHelp(char** argv) {
|
||||
std::cerr << "Usage: Specify an action and a module name in any order.\n"
|
||||
<< "The action can be any of:\n"
|
||||
|
||||
<< " --help\n"
|
||||
<< " Print this help and exit.\n"
|
||||
|
||||
<< " --list-functions\n"
|
||||
<< " List functions in the given module\n"
|
||||
|
||||
<< "The module name must be a file name.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace wasm {
|
||||
|
||||
class FormatConverter {
|
||||
public:
|
||||
explicit FormatConverter(std::string path) {
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
std::cerr << "Failed to open " << path << "!\n";
|
||||
return;
|
||||
}
|
||||
raw_bytes_ = std::vector<byte>(std::istreambuf_iterator<char>(input), {});
|
||||
if (raw_bytes_.size() < 8 || raw_bytes_[0] != 0 || raw_bytes_[1] != 'a' ||
|
||||
raw_bytes_[2] != 's' || raw_bytes_[3] != 'm') {
|
||||
std::cerr << "That's not a Wasm module!\n";
|
||||
return;
|
||||
}
|
||||
base::Vector<const byte> wire_bytes(raw_bytes_.data(), raw_bytes_.size());
|
||||
ModuleResult result =
|
||||
DecodeWasmModuleForDisassembler(start(), end(), &allocator_);
|
||||
if (result.failed()) {
|
||||
WasmError error = result.error();
|
||||
std::cerr << "Decoding error: " << error.message() << " at offset "
|
||||
<< error.offset() << "\n";
|
||||
// TODO(jkummerow): Show some disassembly.
|
||||
return;
|
||||
}
|
||||
ok_ = true;
|
||||
module_ = result.value();
|
||||
names_provider_ =
|
||||
std::make_unique<NamesProvider>(module_.get(), wire_bytes);
|
||||
}
|
||||
|
||||
bool ok() const { return ok_; }
|
||||
|
||||
void ListFunctions() {
|
||||
DCHECK(ok_);
|
||||
const WasmModule* m = module();
|
||||
uint32_t num_functions = static_cast<uint32_t>(m->functions.size());
|
||||
std::cout << "There are " << num_functions << " functions ("
|
||||
<< m->num_imported_functions << " imported, "
|
||||
<< m->num_declared_functions
|
||||
<< " locally defined); the following have names:\n";
|
||||
for (uint32_t i = 0; i < num_functions; i++) {
|
||||
StringBuilder sb;
|
||||
names()->PrintFunctionName(sb, i);
|
||||
if (sb.length() == 0) continue;
|
||||
std::string name(sb.start(), sb.length());
|
||||
std::cout << i << " " << name << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
byte* start() { return raw_bytes_.data(); }
|
||||
byte* end() { return start() + raw_bytes_.size(); }
|
||||
const WasmModule* module() { return module_.get(); }
|
||||
NamesProvider* names() { return names_provider_.get(); }
|
||||
|
||||
AccountingAllocator allocator_;
|
||||
bool ok_{false};
|
||||
std::vector<byte> raw_bytes_;
|
||||
std::shared_ptr<WasmModule> module_;
|
||||
std::unique_ptr<NamesProvider> names_provider_;
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
using FormatConverter = v8::internal::wasm::FormatConverter;
|
||||
|
||||
enum class Action {
|
||||
kUnset,
|
||||
kHelp,
|
||||
kListFunctions,
|
||||
};
|
||||
|
||||
struct Options {
|
||||
const char* filename = nullptr;
|
||||
Action action = Action::kUnset;
|
||||
};
|
||||
|
||||
void ListFunctions(const Options& options) {
|
||||
FormatConverter fc(options.filename);
|
||||
if (fc.ok()) fc.ListFunctions();
|
||||
}
|
||||
|
||||
int ParseOptions(int argc, char** argv, Options* options) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0 ||
|
||||
strcmp(argv[i], "help") == 0) {
|
||||
options->action = Action::kHelp;
|
||||
} else if (strcmp(argv[i], "--list-functions") == 0) {
|
||||
options->action = Action::kListFunctions;
|
||||
} else if (options->filename != nullptr) {
|
||||
return PrintHelp(argv);
|
||||
} else {
|
||||
options->filename = argv[i];
|
||||
}
|
||||
}
|
||||
if (options->action == Action::kUnset || options->filename == nullptr) {
|
||||
return PrintHelp(argv);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
Options options;
|
||||
if (ParseOptions(argc, argv, &options) != 0) return 1;
|
||||
// Bootstrap the basics.
|
||||
v8::V8::InitializeICUDefaultLocation(argv[0]);
|
||||
v8::V8::InitializeExternalStartupData(argv[0]);
|
||||
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
|
||||
v8::V8::InitializePlatform(platform.get());
|
||||
#ifdef V8_ENABLE_SANDBOX
|
||||
if (!v8::V8::InitializeSandbox()) {
|
||||
fprintf(stderr, "Error initializing the V8 sandbox\n");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
v8::V8::Initialize();
|
||||
|
||||
switch (options.action) {
|
||||
// clang-format off
|
||||
case Action::kHelp: PrintHelp(argv); break;
|
||||
case Action::kListFunctions: ListFunctions(options); break;
|
||||
case Action::kUnset: UNREACHABLE();
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
v8::V8::Dispose();
|
||||
v8::V8::DisposePlatform();
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user