From 9253a40c099746168794d130db52a823f5a36440 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Thu, 22 Jan 2026 08:25:39 -0700 Subject: [PATCH 1/7] fix(profiling): Windows extern statics need __declspec(dllimport) If they don't have this, they crash when dereferencing the pointer. --- build-common/src/cbindgen.rs | 39 ++++++++++++++++++++++++++++++++++++ windows/build-artifacts.ps1 | 28 ++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/build-common/src/cbindgen.rs b/build-common/src/cbindgen.rs index 07275dac9c..d821b8fc79 100644 --- a/build-common/src/cbindgen.rs +++ b/build-common/src/cbindgen.rs @@ -127,9 +127,48 @@ pub fn generate_header(crate_dir: PathBuf, header_name: &str, output_base_dir: P .expect("Unable to generate bindings") .write_to_file(output_path); + if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") { + post_process_windows_header(&output_path); + } + println!("cargo:rerun-if-changed=cbindgen.toml"); } +fn post_process_windows_header(output_path: &Path) { + let contents = fs::read_to_string(output_path).expect("Failed to read generated header"); + let mut changed = false; + let mut updated = String::with_capacity(contents.len()); + + for line in contents.lines() { + let indent_len = line + .chars() + .take_while(|c| *c == ' ' || *c == '\t') + .count(); + let (indent, rest) = line.split_at(indent_len); + + let should_patch = rest.starts_with("extern ") + && rest.ends_with(';') + && !rest.contains('(') + && !rest.starts_with("extern \"C\"") + && !rest.contains("__declspec(dllimport)"); + + if should_patch { + let rest = rest.strip_prefix("extern ").unwrap_or(rest); + updated.push_str(indent); + updated.push_str("extern __declspec(dllimport) "); + updated.push_str(rest); + changed = true; + } else { + updated.push_str(line); + } + updated.push('\n'); + } + + if changed { + fs::write(output_path, updated).expect("Failed to update generated header"); + } +} + /// Copies header files from the source directory to the destination directory. /// It considers all .*h* files in the source directory. /// This behaves in a similar way as `generate_and_configure_header`. diff --git a/windows/build-artifacts.ps1 b/windows/build-artifacts.ps1 index 551d68815c..851e7351fa 100644 --- a/windows/build-artifacts.ps1 +++ b/windows/build-artifacts.ps1 @@ -8,6 +8,28 @@ function Invoke-Call { } } +function Add-DllImportToGlobals { + param ( + [string]$HeaderPath + ) + $changed = $false + $updated = foreach ($line in Get-Content $HeaderPath) { + if ($line -match '^\s*extern\s+' -and + $line -match ';$' -and + $line -notmatch '\(' -and + $line -notmatch 'extern "C"' -and + $line -notmatch '__declspec\(dllimport\)') { + $changed = $true + $line -replace '^(\s*)extern\s+', '$1extern __declspec(dllimport) ' + } else { + $line + } + } + if ($changed) { + Set-Content -Path $HeaderPath -Value $updated + } +} + $output_dir = $args[0] if ([string]::IsNullOrEmpty($output_dir)) { @@ -61,6 +83,12 @@ Invoke-Call -ScriptBlock { cbindgen --crate libdd-telemetry-ffi --config libdd-t Invoke-Call -ScriptBlock { cbindgen --crate libdd-data-pipeline-ffi --config libdd-data-pipeline-ffi/cbindgen.toml --output $output_dir"\data-pipeline.h" } Invoke-Call -ScriptBlock { cbindgen --crate libdd-crashtracker-ffi --config libdd-crashtracker-ffi/cbindgen.toml --output $output_dir"\crashtracker.h" } Invoke-Call -ScriptBlock { cbindgen --crate libdd-library-config-ffi --config libdd-library-config-ffi/cbindgen.toml --output $output_dir"\library-config.h" } +Add-DllImportToGlobals $output_dir"\common.h" +Add-DllImportToGlobals $output_dir"\profiling.h" +Add-DllImportToGlobals $output_dir"\telemetry.h" +Add-DllImportToGlobals $output_dir"\data-pipeline.h" +Add-DllImportToGlobals $output_dir"\crashtracker.h" +Add-DllImportToGlobals $output_dir"\library-config.h" Invoke-Call -ScriptBlock { .\target\release\dedup_headers $output_dir"\common.h" $output_dir"\profiling.h" $output_dir"\telemetry.h" $output_dir"\data-pipeline.h" $output_dir"\crashtracker.h" $output_dir"\library-config.h"} Write-Host "Build finished" -ForegroundColor Magenta From a68d1205cd8c817ec248ff74638eac3d1abca79a Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Thu, 22 Jan 2026 08:31:43 -0700 Subject: [PATCH 2/7] fix: build.rs --- build-common/src/cbindgen.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/build-common/src/cbindgen.rs b/build-common/src/cbindgen.rs index d821b8fc79..2276d391f5 100644 --- a/build-common/src/cbindgen.rs +++ b/build-common/src/cbindgen.rs @@ -125,7 +125,7 @@ pub fn generate_header(crate_dir: PathBuf, header_name: &str, output_base_dir: P .with_config(Config::from_root_or_default(&crate_dir)) .generate() .expect("Unable to generate bindings") - .write_to_file(output_path); + .write_to_file(&output_path); if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") { post_process_windows_header(&output_path); @@ -140,10 +140,7 @@ fn post_process_windows_header(output_path: &Path) { let mut updated = String::with_capacity(contents.len()); for line in contents.lines() { - let indent_len = line - .chars() - .take_while(|c| *c == ' ' || *c == '\t') - .count(); + let indent_len = line.chars().take_while(|c| *c == ' ' || *c == '\t').count(); let (indent, rest) = line.split_at(indent_len); let should_patch = rest.starts_with("extern ") From b0de00ee983d565cde5accb2c205269ea7b701e4 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Thu, 22 Jan 2026 08:35:30 -0700 Subject: [PATCH 3/7] revert changes to cbindgen.rs, Windows doesn't use this --- build-common/src/cbindgen.rs | 38 +----------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/build-common/src/cbindgen.rs b/build-common/src/cbindgen.rs index 2276d391f5..07275dac9c 100644 --- a/build-common/src/cbindgen.rs +++ b/build-common/src/cbindgen.rs @@ -125,47 +125,11 @@ pub fn generate_header(crate_dir: PathBuf, header_name: &str, output_base_dir: P .with_config(Config::from_root_or_default(&crate_dir)) .generate() .expect("Unable to generate bindings") - .write_to_file(&output_path); - - if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") { - post_process_windows_header(&output_path); - } + .write_to_file(output_path); println!("cargo:rerun-if-changed=cbindgen.toml"); } -fn post_process_windows_header(output_path: &Path) { - let contents = fs::read_to_string(output_path).expect("Failed to read generated header"); - let mut changed = false; - let mut updated = String::with_capacity(contents.len()); - - for line in contents.lines() { - let indent_len = line.chars().take_while(|c| *c == ' ' || *c == '\t').count(); - let (indent, rest) = line.split_at(indent_len); - - let should_patch = rest.starts_with("extern ") - && rest.ends_with(';') - && !rest.contains('(') - && !rest.starts_with("extern \"C\"") - && !rest.contains("__declspec(dllimport)"); - - if should_patch { - let rest = rest.strip_prefix("extern ").unwrap_or(rest); - updated.push_str(indent); - updated.push_str("extern __declspec(dllimport) "); - updated.push_str(rest); - changed = true; - } else { - updated.push_str(line); - } - updated.push('\n'); - } - - if changed { - fs::write(output_path, updated).expect("Failed to update generated header"); - } -} - /// Copies header files from the source directory to the destination directory. /// It considers all .*h* files in the source directory. /// This behaves in a similar way as `generate_and_configure_header`. From ed44dcda3cdc559b5d55e3cb70871c7f42147d7c Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Sun, 25 Jan 2026 20:32:15 -0700 Subject: [PATCH 4/7] fix: rewrite headers in utf8 --- windows/build-artifacts.ps1 | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/windows/build-artifacts.ps1 b/windows/build-artifacts.ps1 index 851e7351fa..0284833075 100644 --- a/windows/build-artifacts.ps1 +++ b/windows/build-artifacts.ps1 @@ -12,21 +12,16 @@ function Add-DllImportToGlobals { param ( [string]$HeaderPath ) - $changed = $false - $updated = foreach ($line in Get-Content $HeaderPath) { - if ($line -match '^\s*extern\s+' -and - $line -match ';$' -and - $line -notmatch '\(' -and - $line -notmatch 'extern "C"' -and - $line -notmatch '__declspec\(dllimport\)') { - $changed = $true - $line -replace '^(\s*)extern\s+', '$1extern __declspec(dllimport) ' - } else { - $line - } - } - if ($changed) { - Set-Content -Path $HeaderPath -Value $updated + $content = [System.IO.File]::ReadAllText($HeaderPath) + $pattern = '(?m)^(\s*)extern\s+(?!\"C\")(?!.*__declspec\(dllimport\))(?!.*\()(.+;)$' + $updated = [System.Text.RegularExpressions.Regex]::Replace( + $content, + $pattern, + '$1extern __declspec(dllimport) $2' + ) + if ($updated -ne $content) { + $utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($HeaderPath, $updated, $utf8NoBom) } } From c96e9b64f4435653395f5885d20c716d02345c38 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 27 Jan 2026 17:14:49 -0700 Subject: [PATCH 5/7] fix: .NET on Linux headers --- libdd-profiling-ffi/cbindgen.toml | 6 ++++++ windows/build-artifacts.ps1 | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/libdd-profiling-ffi/cbindgen.toml b/libdd-profiling-ffi/cbindgen.toml index c364b220aa..393e79755c 100644 --- a/libdd-profiling-ffi/cbindgen.toml +++ b/libdd-profiling-ffi/cbindgen.toml @@ -22,6 +22,12 @@ typedef struct ddog_prof_Function2 *ddog_prof_FunctionId2; #include "common.h" struct TokioCancellationToken; + +#ifdef _WIN32 +#define LIBDD_DLLIMPORT __declspec(dllimport) +#else +#define LIBDD_DLLIMPORT +#endif """ [export] diff --git a/windows/build-artifacts.ps1 b/windows/build-artifacts.ps1 index 0284833075..90a64dbef6 100644 --- a/windows/build-artifacts.ps1 +++ b/windows/build-artifacts.ps1 @@ -13,11 +13,11 @@ function Add-DllImportToGlobals { [string]$HeaderPath ) $content = [System.IO.File]::ReadAllText($HeaderPath) - $pattern = '(?m)^(\s*)extern\s+(?!\"C\")(?!.*__declspec\(dllimport\))(?!.*\()(.+;)$' + $pattern = '(?m)^(\s*)extern\s+(?!\"C\")(?!.*LIBDD_DLLIMPORT)(?!.*\()(.+;)$' $updated = [System.Text.RegularExpressions.Regex]::Replace( $content, $pattern, - '$1extern __declspec(dllimport) $2' + '$1extern LIBDD_DLLIMPORT $2' ) if ($updated -ne $content) { $utf8NoBom = New-Object System.Text.UTF8Encoding($false) From d844b01d2a994c54c0553b4b500499c57c5410a4 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Thu, 29 Jan 2026 10:51:25 +0100 Subject: [PATCH 6/7] Add support to adjust extern symbols in builder --- Cargo.lock | 1 + builder/Cargo.toml | 1 + builder/src/common.rs | 6 +++--- builder/src/crashtracker.rs | 5 +++-- builder/src/profiling.rs | 5 +++-- builder/src/utils.rs | 35 +++++++++++++++++++++++++++++++++++ 6 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a7065dfc0..5ace1f51bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -764,6 +764,7 @@ dependencies = [ "build_common", "cmake", "pico-args", + "regex", "serde", "tar", "toml", diff --git a/builder/Cargo.toml b/builder/Cargo.toml index fbbfe6a6b2..264e4c8599 100644 --- a/builder/Cargo.toml +++ b/builder/Cargo.toml @@ -44,6 +44,7 @@ tar = "0.4.41" tools = { path = "../tools" } toml = "0.8.19" serde = "1.0.209" +regex = "1.10" [[bin]] name = "release" diff --git a/builder/src/common.rs b/builder/src/common.rs index f778b51600..a9d3cb1eed 100644 --- a/builder/src/common.rs +++ b/builder/src/common.rs @@ -3,9 +3,8 @@ use crate::arch; use crate::module::Module; -use crate::utils::project_root; +use crate::utils::{adjust_extern_symbols, project_root}; use anyhow::Result; -use std::fs; use std::path::PathBuf; use std::process::Command; use std::rc::Rc; @@ -41,7 +40,8 @@ impl Module for Common { let target_path: PathBuf = [self.target_include.as_ref(), "common.h"].iter().collect(); let origin_path: PathBuf = [self.source_include.as_ref(), "common.h"].iter().collect(); - fs::copy(origin_path, target_path).expect("Failed to copy common.h"); + adjust_extern_symbols(&origin_path, &target_path).expect("Failed to adjust extern symbols"); + Ok(()) } } diff --git a/builder/src/crashtracker.rs b/builder/src/crashtracker.rs index 45737829e8..7bb5a45570 100644 --- a/builder/src/crashtracker.rs +++ b/builder/src/crashtracker.rs @@ -3,7 +3,7 @@ use crate::arch; use crate::module::Module; -use crate::utils::project_root; +use crate::utils::{adjust_extern_symbols, project_root}; use anyhow::Result; use std::fs; use std::path::PathBuf; @@ -106,7 +106,8 @@ impl CrashTracker { .collect(); let headers = vec![target_path.to_str().unwrap()]; - fs::copy(origin_path, &target_path).expect("Failed to copy crashtracker.h"); + adjust_extern_symbols(&origin_path, &target_path) + .expect("Failed to adjust extern symbols for crashtracker.h"); dedup_headers(self.base_header.as_ref(), &headers); diff --git a/builder/src/profiling.rs b/builder/src/profiling.rs index a2b3f7fa35..77a190bc1f 100644 --- a/builder/src/profiling.rs +++ b/builder/src/profiling.rs @@ -3,7 +3,7 @@ use crate::arch; use crate::module::Module; -use crate::utils::{file_replace, project_root}; +use crate::utils::{adjust_extern_symbols, file_replace, project_root}; use anyhow::Result; use serde::Deserialize; use std::ffi::OsStr; @@ -67,7 +67,8 @@ impl Profiling { for header in &headers { origin_path.set_file_name(header); target_path.set_file_name(header); - fs::copy(&origin_path, &target_path).expect("Failed to copy the header"); + adjust_extern_symbols(&origin_path, &target_path) + .expect("Failed to adjust extern symbols"); // Exclude blazesym header from deduplication if !target_path.to_str().unwrap().contains("blazesym.h") { diff --git a/builder/src/utils.rs b/builder/src/utils.rs index 89200fb022..6b9bca2445 100644 --- a/builder/src/utils.rs +++ b/builder/src/utils.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use anyhow::{anyhow, Result}; +use regex::Regex; use std::fs::{self, OpenOptions}; use std::io::Write; use std::path::{Path, PathBuf}; @@ -26,3 +27,37 @@ pub fn project_root() -> PathBuf { .unwrap() .to_path_buf() } + +pub(crate) fn adjust_extern_symbols( + file_in: impl AsRef, + file_out: impl AsRef, +) -> Result<()> { + let content = fs::read_to_string(file_in)?; + let re = Regex::new(r#"(?m)^(\s*)extern\s+(.+;)$"#).unwrap(); + + // Replace function using captures + let new_content = re.replace_all(&content, |caps: ®ex::Captures| { + let full_match = caps.get(0).unwrap().as_str(); + let indent = &caps[1]; + let declaration = &caps[2]; + + // Skip if it's extern "C", already has LIBDD_DLLIMPORT, or contains '(' (function) + if full_match.contains("extern \"C\"") + || full_match.contains("LIBDD_DLLIMPORT") + || full_match.contains('(') + { + return full_match.to_string(); + } + + // Keep indent + "extern " + "LIBDD_DLL_IMPORT " + declaration + format!("{}extern LIBDD_DLLIMPORT {}", indent, declaration) + }); + + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(file_out)?; + file.write_all(new_content.as_bytes()) + .map_err(|err| anyhow!("failed to write file: {}", err)) +} From 2bc74c66ae97d90a673fea7bf912326a9a819aa9 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Thu, 29 Jan 2026 11:21:16 +0100 Subject: [PATCH 7/7] Move LIBDD_DLLIMPORT in common --- libdd-common-ffi/cbindgen.toml | 6 ++++++ libdd-profiling-ffi/cbindgen.toml | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libdd-common-ffi/cbindgen.toml b/libdd-common-ffi/cbindgen.toml index 02c4fc6449..a5bef244de 100644 --- a/libdd-common-ffi/cbindgen.toml +++ b/libdd-common-ffi/cbindgen.toml @@ -42,6 +42,12 @@ after_includes = """ # define DDOG_CHECK_RETURN __attribute__((__warn_unused_result__)) #else # define DDOG_CHECK_RETURN +#endif + +#ifdef _WIN32 +#define LIBDD_DLLIMPORT __declspec(dllimport) +#else +#define LIBDD_DLLIMPORT #endif""" [export] diff --git a/libdd-profiling-ffi/cbindgen.toml b/libdd-profiling-ffi/cbindgen.toml index 393e79755c..c364b220aa 100644 --- a/libdd-profiling-ffi/cbindgen.toml +++ b/libdd-profiling-ffi/cbindgen.toml @@ -22,12 +22,6 @@ typedef struct ddog_prof_Function2 *ddog_prof_FunctionId2; #include "common.h" struct TokioCancellationToken; - -#ifdef _WIN32 -#define LIBDD_DLLIMPORT __declspec(dllimport) -#else -#define LIBDD_DLLIMPORT -#endif """ [export]