Skip to content

Instantly share code, notes, and snippets.

@AlexTMjugador
Last active November 23, 2024 23:13
Show Gist options
  • Save AlexTMjugador/b7c61590636838d80b11259d98df58d7 to your computer and use it in GitHub Desktop.
Save AlexTMjugador/b7c61590636838d80b11259d98df58d7 to your computer and use it in GitHub Desktop.
Minimalistic and lightweight Rust code to fetch the build creation timestamp of the current executable for Windows platforms, without requiring any build-time script or configuration.
/// Minimalistic and lightweight Rust code to fetch the build creation timestamp of the current
/// executable for Windows platforms, without requiring any build-time script or configuration.
///
/// Only the following is required in your `Cargo.toml`:
/// ```toml
/// [dependencies]
/// windows-sys = { version = "0.59.0", features = ["Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_System_ProcessStatus"] }
/// tz-rs = { version = "0.7.0", default-features = false }
/// ```
fn build_date_time() -> Option<UtcDateTime> {
use std::{mem::MaybeUninit, ptr, ptr::NonNull, slice};
use windows_sys::Win32::{
Foundation::HMODULE,
System::{
LibraryLoader::GetModuleHandleW,
ProcessStatus::{GetModuleInformation, MODULEINFO},
Threading::GetCurrentProcess,
},
};
// SAFETY: function called according to its contract. No need to free the returned handle
let process_module_handle: HMODULE =
NonNull::new(unsafe { GetModuleHandleW(ptr::null()) })?.as_ptr();
let mut process_module_info = MaybeUninit::uninit();
if unsafe {
// SAFETY: GetCurrentProcess always returns a valid handle for the current process.
// GetModuleInformation is called according to its contract.
GetModuleInformation(
GetCurrentProcess(),
process_module_handle,
process_module_info.as_mut_ptr(),
size_of::<MODULEINFO>() as _,
)
} == 0
{
return None;
}
// SAFETY: binary_module_info was initialized by the successful GetModuleInformation call above
let process_module_info = unsafe { process_module_info.assume_init() };
// SAFETY: the base address of a loaded module is where its module image, in PE format, is loaded
// in memory. The size of the image is the size of the loaded module's PE image in memory.
// References:
// - https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-moduleinfo
// - https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-windows-specific-fields-image-only
let process_pe_image = unsafe {
slice::from_raw_parts(
process_module_info.lpBaseOfDll.cast::<u8>(),
process_module_info.SizeOfImage as _,
)
};
// PE images are prefixed by an MS-DOS stub executable that contains the offset to the PE COFF header
// at offset 0x3C. In turn, the PE COFF header contains a 32-bit Unix timestamp for the executable
// creation time at offset 0x08 (4-byte signature + 0x04). PE executables are always little-endian.
// See: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#file-headers
let pe_header_offset = u32::from_le_bytes(
process_pe_image[0x3C..0x3C + size_of::<u32>()]
.try_into()
.unwrap(),
) as usize;
let coff_header_timestamp = u32::from_le_bytes(
process_pe_image[pe_header_offset + 0x08..pe_header_offset + 0x08 + size_of::<u32>()]
.try_into()
.unwrap(),
);
UtcDateTime::from_timespec(coff_header_timestamp as _, 0).ok()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment