/// Tool to compute the same hash as ClickHouse's getHashOfLoadedBinary() from a file. /// Useful for cross-compilation when you can't execute the target binary. /// /// NOTE: This file is intentionally standalone with no ClickHouse dependencies. /// We cannot reuse src/Common/SipHash.h because it depends on ClickHouse-specific /// headers (base/types.h, base/extended_types.h, wide_integer_impl.h, etc.) that /// pull in boost and other dependencies not available during cross-compilation. /// This tool must be compilable with just the host compiler. #include #include #include #include #include #include #include #include #include namespace { uint64_t rotl64(uint64_t value, unsigned int count) { return (value << count) | (value >> (64U - count)); } /// Standalone SipHash implementation (SipHash 2-4 variant with 128-bit output). /// This is a simplified version of ClickHouse's SipHash, producing identical results. class SipHash { private: uint64_t v0; uint64_t v1; uint64_t v2; uint64_t v3; uint64_t cnt; union { uint64_t current_word; uint8_t current_bytes[8]; }; void sipround() { v0 += v1; v1 = rotl64(v1, 13); v1 ^= v0; v0 = rotl64(v0, 32); v2 += v3; v3 = rotl64(v3, 16); v3 ^= v2; v0 += v3; v3 = rotl64(v3, 21); v3 ^= v0; v2 += v1; v1 = rotl64(v1, 17); v1 ^= v2; v2 = rotl64(v2, 32); } void finalize() { current_bytes[7] = static_cast(cnt); v3 ^= current_word; sipround(); sipround(); v0 ^= current_word; v2 ^= 0xffU; sipround(); sipround(); sipround(); sipround(); } public: SipHash() : v0(0x736f6d6570736575ULL) , v1(0x646f72616e646f6dULL) , v2(0x6c7967656e657261ULL) , v3(0x7465646279746573ULL) , cnt(0) , current_word(0) { } void update(const char * data, uint64_t size) { const char * end = data + size; if (cnt & 7) { while ((cnt & 7) && data < end) { current_bytes[cnt & 7] = static_cast(*data); ++data; ++cnt; } if (cnt & 7) return; v3 ^= current_word; sipround(); sipround(); v0 ^= current_word; } cnt += static_cast(end - data); while (data + 8 <= end) { memcpy(¤t_word, data, 8); v3 ^= current_word; sipround(); sipround(); v0 ^= current_word; data += 8; } current_word = 0; switch (end - data) { case 7: current_bytes[6] = static_cast(data[6]); [[fallthrough]]; case 6: current_bytes[5] = static_cast(data[5]); [[fallthrough]]; case 5: current_bytes[4] = static_cast(data[4]); [[fallthrough]]; case 4: current_bytes[3] = static_cast(data[3]); [[fallthrough]]; case 3: current_bytes[2] = static_cast(data[2]); [[fallthrough]]; case 2: current_bytes[1] = static_cast(data[1]); [[fallthrough]]; case 1: current_bytes[0] = static_cast(data[0]); [[fallthrough]]; default: break; } } void update(uint64_t value) { update(reinterpret_cast(&value), sizeof(value)); } void get128(uint64_t & lo, uint64_t & hi) { finalize(); lo = v0 ^ v1; hi = v2 ^ v3; } }; template bool hashExecutableSegments(std::istream & in, SipHash & hash) { Ehdr ehdr; in.seekg(0); if (!in.read(reinterpret_cast(&ehdr), sizeof(ehdr))) { std::cerr << "Failed to read ELF header\n"; return false; } for (size_t i = 0; i < ehdr.e_phnum; ++i) { Phdr phdr; in.seekg(static_cast(ehdr.e_phoff + i * static_cast(ehdr.e_phentsize))); if (!in.read(reinterpret_cast(&phdr), sizeof(phdr))) { std::cerr << "Failed to read program header\n"; return false; } if (phdr.p_type == PT_LOAD && (phdr.p_flags & PF_X)) { std::cerr << "Found executable segment: offset=0x" << std::hex << phdr.p_offset << ", filesz=0x" << phdr.p_filesz << std::dec << " (" << phdr.p_filesz << " bytes)\n"; const uint64_t filesz = phdr.p_filesz; hash.update(filesz); in.seekg(static_cast(phdr.p_offset)); std::vector buffer(8UL * 1024UL * 1024UL); uint64_t remaining = filesz; while (remaining > 0) { const size_t to_read = std::min(remaining, static_cast(buffer.size())); if (!in.read(buffer.data(), static_cast(to_read))) { std::cerr << "Failed to read segment data\n"; return false; } hash.update(buffer.data(), to_read); remaining -= to_read; } } } return true; } bool hashElfFile(std::istream & in, SipHash & hash) { unsigned char e_ident[EI_NIDENT]; if (!in.read(reinterpret_cast(e_ident), EI_NIDENT)) { std::cerr << "Failed to read ELF ident\n"; return false; } if (memcmp(e_ident, ELFMAG, SELFMAG) != 0) { std::cerr << "Not an ELF file\n"; return false; } if (e_ident[EI_CLASS] == ELFCLASS64) return hashExecutableSegments(in, hash); else return hashExecutableSegments(in, hash); } } int main(int argc, char ** argv) { try { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " \n" << "\nComputes the same hash as 'clickhouse hash-binary' but from a file.\n" << "Useful for cross-compilation when you can't execute the target binary.\n"; return 1; } std::ifstream file(argv[1], std::ios::binary); if (!file) { std::cerr << "Failed to open file: " << argv[1] << "\n"; return 1; } SipHash hash; if (!hashElfFile(file, hash)) return 1; uint64_t lo = 0; uint64_t hi = 0; hash.get128(lo, hi); /// Output format matches ClickHouse's getHexUIntUppercase for UInt128 std::cout << std::uppercase << std::hex << std::setfill('0') << std::setw(16) << hi << std::setw(16) << lo; return 0; } catch (const std::exception & e) { std::cerr << "Error: " << e.what() << "\n"; return 1; } }