// Copyright (c) 2011 The Chromium 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 #include #include #include #include #include #include #include #include "base/at_exit.h" #include "base/command_line.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/memory_mapped_file.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "courgette/assembly_program.h" #include "courgette/courgette.h" #include "courgette/courgette_flow.h" #include "courgette/encoded_program.h" #include "courgette/program_detector.h" #include "courgette/streams.h" #include "courgette/third_party/bsdiff/bsdiff.h" namespace { using courgette::CourgetteFlow; const char kUsageGen[] = "-gen "; const char kUsageApply[] = "-apply "; const char kUsageGenbsdiff[] = "-genbsdiff "; const char kUsageApplybsdiff[] = "-applybsdiff "; const char kUsageSupported[] = "-supported "; const char kUsageDis[] = "-dis "; const char kUsageAsm[] = "-asm "; const char kUsageDisadj[] = "-disadj "; const char kUsageGen1[] = "-gen1[au] "; /******** Utilities to print help and exit ********/ void PrintHelp() { fprintf(stderr, "Main Usage:\n"); for (auto usage : {kUsageGen, kUsageApply, kUsageGenbsdiff, kUsageApplybsdiff}) { fprintf(stderr, " courgette %s\n", usage); } fprintf(stderr, "Diagnosis Usage:\n"); for (auto usage : {kUsageSupported, kUsageDis, kUsageAsm, kUsageDisadj, kUsageGen1}) { fprintf(stderr, " courgette %s\n", usage); } } void UsageProblem(const char* message) { fprintf(stderr, "%s", message); fprintf(stderr, "\n"); PrintHelp(); exit(1); } void Problem(const char* format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); fprintf(stderr, "\n"); va_end(args); exit(1); } /******** BufferedFileReader ********/ // A file reader that calls Problem() on failure. class BufferedFileReader : public courgette::BasicBuffer { public: BufferedFileReader(const base::FilePath& file_name, const char* kind) { if (!buffer_.Initialize(file_name)) Problem("Can't read %s file.", kind); } BufferedFileReader(const BufferedFileReader&) = delete; BufferedFileReader& operator=(const BufferedFileReader&) = delete; ~BufferedFileReader() override = default; // courgette::BasicBuffer: const uint8_t* data() const override { return buffer_.data(); } size_t length() const override { return buffer_.length(); } private: base::MemoryMappedFile buffer_; }; /******** Various helpers ********/ void WriteSinkToFile(const courgette::SinkStream* sink, const base::FilePath& output_file) { int count = base::WriteFile(output_file, reinterpret_cast(sink->Buffer()), static_cast(sink->Length())); if (count == -1) Problem("Can't write output."); if (static_cast(count) != sink->Length()) Problem("Incomplete write."); } bool Supported(const base::FilePath& input_file) { bool result = false; BufferedFileReader buffer(input_file, "input"); courgette::ExecutableType type; size_t detected_length; DetectExecutableType(buffer.data(), buffer.length(), &type, &detected_length); // If the detection fails, we just fall back on UNKNOWN std::string format = "Unsupported"; switch (type) { case courgette::EXE_UNKNOWN: break; case courgette::EXE_WIN_32_X86: format = "Windows 32 PE"; result = true; break; case courgette::EXE_ELF_32_X86: format = "ELF 32 X86"; result = true; break; case courgette::EXE_WIN_32_X64: format = "Windows 64 PE"; result = true; break; } printf("%s Executable\n", format.c_str()); return result; } void Disassemble(const base::FilePath& input_file, const base::FilePath& output_file) { CourgetteFlow flow; BufferedFileReader input_buffer(input_file, flow.name(flow.ONLY)); flow.ReadDisassemblerFromBuffer(flow.ONLY, input_buffer); flow.CreateAssemblyProgramFromDisassembler(flow.ONLY, false); flow.CreateEncodedProgramFromDisassemblerAndAssemblyProgram(flow.ONLY); flow.DestroyDisassembler(flow.ONLY); flow.DestroyAssemblyProgram(flow.ONLY); flow.WriteSinkStreamSetFromEncodedProgram(flow.ONLY); flow.DestroyEncodedProgram(flow.ONLY); courgette::SinkStream sink; flow.WriteSinkStreamFromSinkStreamSet(flow.ONLY, &sink); if (flow.failed()) Problem(flow.message().c_str()); WriteSinkToFile(&sink, output_file); } void DisassembleAndAdjust(const base::FilePath& old_file, const base::FilePath& new_file, const base::FilePath& output_file) { // Flow graph and process sequence (DA = Disassembler, AP = AssemblyProgram, // EP = EncodedProgram, Adj = Adjusted): // [1 Old DA] --> [2 Old AP] [4 New AP] <-- [3 New DA] // | | | // | v (move) v // +---> [5 Adj New AP] --> [6 New EP] // (7 Write) CourgetteFlow flow; BufferedFileReader old_buffer(old_file, flow.name(flow.OLD)); BufferedFileReader new_buffer(new_file, flow.name(flow.NEW)); flow.ReadDisassemblerFromBuffer(flow.OLD, old_buffer); // 1 flow.CreateAssemblyProgramFromDisassembler(flow.OLD, true); // 2 flow.DestroyDisassembler(flow.OLD); flow.ReadDisassemblerFromBuffer(flow.NEW, new_buffer); // 3 flow.CreateAssemblyProgramFromDisassembler(flow.NEW, true); // 4 flow.AdjustNewAssemblyProgramToMatchOld(); // 5 flow.DestroyAssemblyProgram(flow.OLD); flow.CreateEncodedProgramFromDisassemblerAndAssemblyProgram(flow.NEW); // 6 flow.DestroyAssemblyProgram(flow.NEW); flow.DestroyDisassembler(flow.NEW); flow.WriteSinkStreamSetFromEncodedProgram(flow.NEW); // 7 flow.DestroyEncodedProgram(flow.NEW); courgette::SinkStream sink; flow.WriteSinkStreamFromSinkStreamSet(flow.NEW, &sink); if (flow.failed()) Problem(flow.message().c_str()); WriteSinkToFile(&sink, output_file); } // Diffs two executable files, write a set of files for the diff, one file per // stream of the EncodedProgram format. Each file is the bsdiff between the // original file's stream and the new file's stream. This is completely // uninteresting to users, but it is handy for seeing how much each which // streams are contributing to the final file size. Adjustment is optional. void DisassembleAdjustDiff(const base::FilePath& old_file, const base::FilePath& new_file, const base::FilePath& output_file_root, bool adjust) { // Same as PatchGeneratorX86_32::Transform(), except Adjust is optional, and // |flow|'s internal SinkStreamSet get used. // Flow graph and process sequence (DA = Disassembler, AP = AssemblyProgram, // EP = EncodedProgram, Adj = Adjusted): // [1 Old DA] --> [2 Old AP] [6 New AP] <-- [5 New DA] // | | | | | // v | | v (move) v // [3 Old EP] <-----+ +->[7 Adj New AP] --> [8 New EP] // (4 Write) (9 Write) CourgetteFlow flow; BufferedFileReader old_buffer(old_file, flow.name(flow.OLD)); BufferedFileReader new_buffer(new_file, flow.name(flow.NEW)); flow.ReadDisassemblerFromBuffer(flow.OLD, old_buffer); // 1 flow.CreateAssemblyProgramFromDisassembler(flow.OLD, adjust); // 2 flow.CreateEncodedProgramFromDisassemblerAndAssemblyProgram(flow.OLD); // 3 flow.DestroyDisassembler(flow.OLD); flow.WriteSinkStreamSetFromEncodedProgram(flow.OLD); // 4 flow.DestroyEncodedProgram(flow.OLD); flow.ReadDisassemblerFromBuffer(flow.NEW, new_buffer); // 5 flow.CreateAssemblyProgramFromDisassembler(flow.NEW, adjust); // 6 if (adjust) flow.AdjustNewAssemblyProgramToMatchOld(); // 7, optional flow.DestroyAssemblyProgram(flow.OLD); flow.CreateEncodedProgramFromDisassemblerAndAssemblyProgram(flow.NEW); // 8 flow.DestroyAssemblyProgram(flow.NEW); flow.DestroyDisassembler(flow.NEW); flow.WriteSinkStreamSetFromEncodedProgram(flow.NEW); // 9 flow.DestroyEncodedProgram(flow.NEW); if (flow.failed()) Problem(flow.message().c_str()); courgette::SinkStream empty_sink; for (int i = 0;; ++i) { courgette::SinkStream* old_stream = flow.data(flow.OLD)->sinks.stream(i); courgette::SinkStream* new_stream = flow.data(flow.NEW)->sinks.stream(i); if (old_stream == nullptr && new_stream == nullptr) break; courgette::SourceStream old_source; courgette::SourceStream new_source; old_source.Init(old_stream ? *old_stream : empty_sink); new_source.Init(new_stream ? *new_stream : empty_sink); courgette::SinkStream patch_stream; bsdiff::BSDiffStatus status = bsdiff::CreateBinaryPatch(&old_source, &new_source, &patch_stream); if (status != bsdiff::OK) Problem("-xxx failed."); std::string append = std::string("-") + base::NumberToString(i); WriteSinkToFile(&patch_stream, output_file_root.InsertBeforeExtensionASCII(append)); } } void Assemble(const base::FilePath& input_file, const base::FilePath& output_file) { CourgetteFlow flow; BufferedFileReader input_buffer(input_file, flow.name(flow.ONLY)); flow.ReadSourceStreamSetFromBuffer(flow.ONLY, input_buffer); flow.ReadEncodedProgramFromSourceStreamSet(flow.ONLY); courgette::SinkStream sink; flow.WriteExecutableFromEncodedProgram(flow.ONLY, &sink); if (flow.failed()) Problem(flow.message().c_str()); WriteSinkToFile(&sink, output_file); } void GenerateEnsemblePatch(const base::FilePath& old_file, const base::FilePath& new_file, const base::FilePath& patch_file) { BufferedFileReader old_buffer(old_file, "'old' input"); BufferedFileReader new_buffer(new_file, "'new' input"); courgette::SourceStream old_stream; courgette::SourceStream new_stream; old_stream.Init(old_buffer.data(), old_buffer.length()); new_stream.Init(new_buffer.data(), new_buffer.length()); courgette::SinkStream patch_stream; courgette::Status status = courgette::GenerateEnsemblePatch(&old_stream, &new_stream, &patch_stream); if (status != courgette::C_OK) Problem("-gen failed."); WriteSinkToFile(&patch_stream, patch_file); } void ApplyEnsemblePatch(const base::FilePath& old_file, const base::FilePath& patch_file, const base::FilePath& new_file) { // We do things a little differently here in order to call the same Courgette // entry point as the installer. That entry point point takes file names and // returns an status code but does not output any diagnostics. courgette::Status status = courgette::ApplyEnsemblePatch( old_file.value().c_str(), patch_file.value().c_str(), new_file.value().c_str()); if (status == courgette::C_OK) return; // Diagnose the error. switch (status) { case courgette::C_BAD_ENSEMBLE_MAGIC: Problem("Not a courgette patch"); break; case courgette::C_BAD_ENSEMBLE_VERSION: Problem("Wrong version patch"); break; case courgette::C_BAD_ENSEMBLE_HEADER: Problem("Corrupt patch"); break; case courgette::C_DISASSEMBLY_FAILED: Problem("Disassembly failed (could be because of memory issues)"); break; case courgette::C_STREAM_ERROR: Problem("Stream error (likely out of memory or disk space)"); break; default: break; } // If we failed due to a missing input file, this will print the message. { BufferedFileReader old_buffer(old_file, "'old' input"); } { BufferedFileReader patch_buffer(patch_file, "'patch' input"); } // Non-input related errors: if (status == courgette::C_WRITE_OPEN_ERROR) Problem("Can't open output"); if (status == courgette::C_WRITE_ERROR) Problem("Can't write output"); Problem("-apply failed."); } void GenerateBSDiffPatch(const base::FilePath& old_file, const base::FilePath& new_file, const base::FilePath& patch_file) { BufferedFileReader old_buffer(old_file, "'old' input"); BufferedFileReader new_buffer(new_file, "'new' input"); courgette::SourceStream old_stream; courgette::SourceStream new_stream; old_stream.Init(old_buffer.data(), old_buffer.length()); new_stream.Init(new_buffer.data(), new_buffer.length()); courgette::SinkStream patch_stream; bsdiff::BSDiffStatus status = bsdiff::CreateBinaryPatch(&old_stream, &new_stream, &patch_stream); if (status != bsdiff::OK) Problem("-genbsdiff failed."); WriteSinkToFile(&patch_stream, patch_file); } void ApplyBSDiffPatch(const base::FilePath& old_file, const base::FilePath& patch_file, const base::FilePath& new_file) { BufferedFileReader old_buffer(old_file, "'old' input"); BufferedFileReader patch_buffer(patch_file, "'patch' input"); courgette::SourceStream old_stream; courgette::SourceStream patch_stream; old_stream.Init(old_buffer.data(), old_buffer.length()); patch_stream.Init(patch_buffer.data(), patch_buffer.length()); courgette::SinkStream new_stream; bsdiff::BSDiffStatus status = bsdiff::ApplyBinaryPatch(&old_stream, &patch_stream, &new_stream); if (status != bsdiff::OK) Problem("-applybsdiff failed."); WriteSinkToFile(&new_stream, new_file); } } // namespace int main(int argc, const char* argv[]) { base::AtExitManager at_exit_manager; base::CommandLine::Init(argc, argv); const base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess(); logging::LoggingSettings settings; if (command_line.HasSwitch("nologfile")) { settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR; } else { settings.logging_dest = logging::LOG_TO_ALL; settings.log_file_path = FILE_PATH_LITERAL("courgette.log"); } std::ignore = logging::InitLogging(settings); logging::SetMinLogLevel(logging::LOG_VERBOSE); bool cmd_sup = command_line.HasSwitch("supported"); bool cmd_dis = command_line.HasSwitch("dis"); bool cmd_asm = command_line.HasSwitch("asm"); bool cmd_disadj = command_line.HasSwitch("disadj"); bool cmd_make_patch = command_line.HasSwitch("gen"); bool cmd_apply_patch = command_line.HasSwitch("apply"); bool cmd_make_bsdiff_patch = command_line.HasSwitch("genbsdiff"); bool cmd_apply_bsdiff_patch = command_line.HasSwitch("applybsdiff"); bool cmd_spread_1_adjusted = command_line.HasSwitch("gen1a"); bool cmd_spread_1_unadjusted = command_line.HasSwitch("gen1u"); std::vector values; const base::CommandLine::StringVector& args = command_line.GetArgs(); for (size_t i = 0; i < args.size(); ++i) { values.push_back(base::FilePath(args[i])); } // '-repeat=N' is for debugging. Running many iterations can reveal leaks and // bugs in cleanup. int repeat_count = 1; std::string repeat_switch = command_line.GetSwitchValueASCII("repeat"); if (!repeat_switch.empty()) if (!base::StringToInt(repeat_switch, &repeat_count)) repeat_count = 1; if (cmd_sup + cmd_dis + cmd_asm + cmd_disadj + cmd_make_patch + cmd_apply_patch + cmd_make_bsdiff_patch + cmd_apply_bsdiff_patch + cmd_spread_1_adjusted + cmd_spread_1_unadjusted != 1) { UsageProblem( "First argument must be one of:\n" " -supported, -asm, -dis, -disadj, -gen, -apply, -genbsdiff," " -applybsdiff, or -gen1[au]."); } while (repeat_count-- > 0) { if (cmd_sup) { if (values.size() != 1) UsageProblem(kUsageSupported); return !Supported(values[0]); } else if (cmd_dis) { if (values.size() != 2) UsageProblem(kUsageDis); Disassemble(values[0], values[1]); } else if (cmd_asm) { if (values.size() != 2) UsageProblem(kUsageAsm); Assemble(values[0], values[1]); } else if (cmd_disadj) { if (values.size() != 3) UsageProblem(kUsageDisadj); DisassembleAndAdjust(values[0], values[1], values[2]); } else if (cmd_make_patch) { if (values.size() != 3) UsageProblem(kUsageGen); GenerateEnsemblePatch(values[0], values[1], values[2]); } else if (cmd_apply_patch) { if (values.size() != 3) UsageProblem(kUsageApply); ApplyEnsemblePatch(values[0], values[1], values[2]); } else if (cmd_make_bsdiff_patch) { if (values.size() != 3) UsageProblem(kUsageGenbsdiff); GenerateBSDiffPatch(values[0], values[1], values[2]); } else if (cmd_apply_bsdiff_patch) { if (values.size() != 3) UsageProblem(kUsageApplybsdiff); ApplyBSDiffPatch(values[0], values[1], values[2]); } else if (cmd_spread_1_adjusted || cmd_spread_1_unadjusted) { if (values.size() != 3) UsageProblem(kUsageGen1); DisassembleAdjustDiff(values[0], values[1], values[2], cmd_spread_1_adjusted); } else { UsageProblem("No operation specified"); } } return 0; }