fix(windows): keep the 8 MiB main-thread stack on legacy builds (0xC00000FD) (#13672)
* fix(windows): keep the 8 MiB main-thread stack on legacy builds
Windows 7 / Server 2012 R2 users on 0.6.58 report the WER dialog "DBX 已停止
工作" with APPCRASH signature dbx.exe / faulting module dbx.exe / exception
c00000fd (STATUS_STACK_OVERFLOW) as soon as they click a database connection
(MySQL, Oracle 11 and Dameng 8 all reproduce), while 0.6.55 is fine.
Root cause: .cargo/config.toml raises the main-thread stack to 8 MiB with
-C link-arg=/STACK:8388608, but Cargo ignores target rustflags whenever a
RUSTFLAGS env var is set. The Win7/Server 2012 R2 jobs set RUSTFLAGS for the
static CRT, so the legacy PE kept the 1 MiB linker default while the standard
Windows build kept 8 MiB. The 0.6.56 connect-path rewrite made the DB query
stack deeper, and the legacy 1 MiB stack overflows on connect.
- repeat -C link-arg=/STACK:8388608 in every workflow RUSTFLAGS that links a
Windows PE (release Win7 app + dbx-web, CI Win7/standard/duckdb jobs, SQL
Server 2000 Win7 test build)
- add the same flag to the legacy target table in .cargo/config.toml for local
builds
- fail the Win7 PE audit when SizeOfStackReserve drops below 8 MiB, so this
cannot silently regress again
- assert the flag stays in the workflows from ci-workflow.test.mjs
* chore(clippy): indent the MariaDB KB doc continuation
main's 7958f7ad9 added an unindented continuation line after a doc list
item, so rust-fmt-clippy now fails on main and on every PR with
clippy::doc_lazy_continuation under -D warnings. Indent the line the way
the lint documents so the build can go green.
---------
Co-authored-by: DBX Agent <agent@dbx.local>
This commit is contained in:
1 parent
c09662d78e
commit
fbb588eecb
8 files changed
+98
-8
No files matched your search
@@ -23,3 +23,11 @@ linker = "rust-lld"
|
|||||||
# Large Rust projects in debug mode overflow the 1 MiB stack when DB query
|
# Large Rust projects in debug mode overflow the 1 MiB stack when DB query
|
||||||
# paths pull in deep serde/driver call stacks. This embeds the size in the PE.
|
# paths pull in deep serde/driver call stacks. This embeds the size in the PE.
|
||||||
rustflags = ["-C", "link-arg=/STACK:8388608"]
|
rustflags = ["-C", "link-arg=/STACK:8388608"]
|
||||||
|
|
||||||
|
# The legacy Windows 7 / Server 2012 R2 target keeps the same 8 MiB main-thread
|
||||||
|
# stack. CI builds it with an explicit RUSTFLAGS env (static CRT), which
|
||||||
|
# replaces this table, so the same link arg is repeated in those workflows and
|
||||||
|
# audited by .github/scripts/assert-win7-pe-compat.ps1. Without it the legacy PE
|
||||||
|
# keeps the 1 MiB default and the app aborts with 0xC00000FD ("DBX 已停止工作").
|
||||||
|
[target.x86_64-win7-windows-msvc]
|
||||||
|
rustflags = ["-C", "link-arg=/STACK:8388608"]
|
||||||
@@ -69,3 +69,38 @@ if ($violations.Count -gt 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "Windows 7 PE import audit passed: $BinaryPath"
|
Write-Host "Windows 7 PE import audit passed: $BinaryPath"
|
||||||
|
|
||||||
|
# Main-thread stack reserve audit. The app needs the 8 MiB stack that
|
||||||
|
# .cargo/config.toml applies to the standard Windows target; the legacy target is
|
||||||
|
# built with an explicit env RUSTFLAGS (static CRT), which replaces the config
|
||||||
|
# rustflags, so the workflow repeats `-C link-arg=/STACK:8388608`. Without it the
|
||||||
|
# PE keeps the 1 MiB linker default and deep connect/serde call stacks abort with
|
||||||
|
# 0xC00000FD ("DBX 已停止工作"), which users reported on Windows 7 / Server 2012 R2.
|
||||||
|
$minimumStackReserve = 8 * 1024 * 1024
|
||||||
|
$image = [System.IO.File]::ReadAllBytes($BinaryPath)
|
||||||
|
if ($image.Length -lt 0x100) {
|
||||||
|
throw "Windows 7 PE audit target is truncated: $BinaryPath"
|
||||||
|
}
|
||||||
|
$peOffset = [System.BitConverter]::ToInt32($image, 0x3C)
|
||||||
|
if ($peOffset -le 0 -or $peOffset + 24 -gt $image.Length) {
|
||||||
|
throw "Invalid PE header offset in ${BinaryPath}"
|
||||||
|
}
|
||||||
|
$peSignature = [System.BitConverter]::ToUInt32($image, $peOffset)
|
||||||
|
if ($peSignature -ne 0x00004550) {
|
||||||
|
throw "Invalid PE signature in ${BinaryPath}"
|
||||||
|
}
|
||||||
|
$optionalHeaderOffset = $peOffset + 24
|
||||||
|
$optionalHeaderMagic = [System.BitConverter]::ToUInt16($image, $optionalHeaderOffset)
|
||||||
|
$stackReserveOffset = $optionalHeaderOffset + 72
|
||||||
|
$stackReserve = if ($optionalHeaderMagic -eq 0x20B) {
|
||||||
|
[System.BitConverter]::ToUInt64($image, $stackReserveOffset)
|
||||||
|
} elseif ($optionalHeaderMagic -eq 0x10B) {
|
||||||
|
[System.BitConverter]::ToUInt32($image, $stackReserveOffset)
|
||||||
|
} else {
|
||||||
|
throw ("Unsupported PE optional header magic 0x{0:X4} in {1}" -f $optionalHeaderMagic, $BinaryPath)
|
||||||
|
}
|
||||||
|
if ($stackReserve -lt $minimumStackReserve) {
|
||||||
|
throw ("Windows 7 main-thread stack reserve is {0} bytes, below the {1}-byte minimum. Add -C link-arg=/STACK:8388608 to the build RUSTFLAGS. Binary: {2}" -f $stackReserve, $minimumStackReserve, $BinaryPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Windows 7 PE stack reserve audit passed: $stackReserve bytes ($BinaryPath)"
|
||||||
@@ -496,3 +496,26 @@ test("path filtering and the planner use the exact test merge first parent", ()
|
|||||||
}
|
}
|
||||||
assert.doesNotMatch(changes, /steps\.filter\.outputs\.windows_win7_bundle|github\.event\.pull_request\.base\.sha|WIN7_CURRENT/);
|
assert.doesNotMatch(changes, /steps\.filter\.outputs\.windows_win7_bundle|github\.event\.pull_request\.base\.sha|WIN7_CURRENT/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("legacy Windows builds keep the 8 MiB main-thread stack", () => {
|
||||||
|
const release = readFileSync(new URL("../workflows/release.yml", import.meta.url), "utf8").replaceAll("\r\n", "\n");
|
||||||
|
const sqlServerUserTest = readFileSync(new URL("../workflows/sqlserver2000-windows-user-test.yml", import.meta.url), "utf8").replaceAll("\r\n", "\n");
|
||||||
|
const stackFlag = /RUSTFLAGS:.*link-arg=\/STACK:8388608/;
|
||||||
|
// An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so every job
|
||||||
|
// that links a Windows PE has to repeat the /STACK link argument; otherwise
|
||||||
|
// the 1 MiB linker default returns and deep DB call stacks abort with
|
||||||
|
// 0xC00000FD ("DBX 已停止工作").
|
||||||
|
for (const name of ["windows-standard-check", "windows-win7-bundle", "duckdb-windows-driver"]) {
|
||||||
|
assert.match(job(name), stackFlag, `${name} must keep the 8 MiB main-thread stack in RUSTFLAGS`);
|
||||||
|
}
|
||||||
|
for (const name of ["build-windows-7-offline", "build-windows-7-static-browser"]) {
|
||||||
|
assert.match(job(name, release), stackFlag, `release job ${name} must keep the 8 MiB main-thread stack in RUSTFLAGS`);
|
||||||
|
}
|
||||||
|
assert.match(job("build-windows-7-x64", sqlServerUserTest), stackFlag, "the SQL Server 2000 Win7 test build must keep the 8 MiB main-thread stack");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the Win7 PE audit fails when the stack reserve regresses", () => {
|
||||||
|
const audit = readFileSync(new URL("./assert-win7-pe-compat.ps1", import.meta.url), "utf8");
|
||||||
|
assert.ok(audit.includes("minimumStackReserve"));
|
||||||
|
assert.ok(audit.includes("link-arg=/STACK:8388608"));
|
||||||
|
});
|
||||||
@@ -963,7 +963,9 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ runner.os }}" = "Windows" ]; then
|
if [ "${{ runner.os }}" = "Windows" ]; then
|
||||||
export RUSTFLAGS="${RUSTFLAGS:+$RUSTFLAGS }-C target-feature=+crt-static"
|
# An env RUSTFLAGS replaces .cargo/config.toml target rustflags; keep
|
||||||
|
# the 8 MiB main-thread stack of the DBX app for the Win7 driver too.
|
||||||
|
export RUSTFLAGS="${RUSTFLAGS:+$RUSTFLAGS }-C target-feature=+crt-static -C link-arg=/STACK:8388608"
|
||||||
fi
|
fi
|
||||||
if [ -n "${{ matrix.manylinux_image }}" ]; then
|
if [ -n "${{ matrix.manylinux_image }}" ]; then
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
|
|||||||
@@ -313,7 +313,11 @@ jobs:
|
|||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
env:
|
env:
|
||||||
CARGO_INCREMENTAL: "0"
|
CARGO_INCREMENTAL: "0"
|
||||||
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
|
# An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so the
|
||||||
|
# 8 MiB main-thread stack must be repeated here: without it the Windows PE
|
||||||
|
# keeps the 1 MiB linker default and deep connect/serde call stacks abort
|
||||||
|
# with 0xC00000FD ("DBX 已停止工作"). Guarded by assert-win7-pe-compat.ps1.
|
||||||
|
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static -C link-arg=/STACK:8388608
|
||||||
RUSTC_WRAPPER: sccache
|
RUSTC_WRAPPER: sccache
|
||||||
# Prefer S3 when available; GitHub's 10 GB cache quota evicts the shared
|
# Prefer S3 when available; GitHub's 10 GB cache quota evicts the shared
|
||||||
# Linux target caches when thousands of GHA sccache entries pile up.
|
# Linux target caches when thousands of GHA sccache entries pile up.
|
||||||
@@ -388,7 +392,11 @@ jobs:
|
|||||||
CARGO_INCREMENTAL: "0"
|
CARGO_INCREMENTAL: "0"
|
||||||
CARGO_PROFILE_RELEASE_LTO: "off"
|
CARGO_PROFILE_RELEASE_LTO: "off"
|
||||||
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "8"
|
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "8"
|
||||||
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
|
# An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so the
|
||||||
|
# 8 MiB main-thread stack must be repeated here: without it the Windows PE
|
||||||
|
# keeps the 1 MiB linker default and deep connect/serde call stacks abort
|
||||||
|
# with 0xC00000FD ("DBX 已停止工作"). Guarded by assert-win7-pe-compat.ps1.
|
||||||
|
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static -C link-arg=/STACK:8388608
|
||||||
RUSTC_WRAPPER: sccache
|
RUSTC_WRAPPER: sccache
|
||||||
# Prefer S3 when available; GitHub's 10 GB cache quota evicts the shared
|
# Prefer S3 when available; GitHub's 10 GB cache quota evicts the shared
|
||||||
# Linux target caches when thousands of GHA sccache entries pile up.
|
# Linux target caches when thousands of GHA sccache entries pile up.
|
||||||
@@ -830,7 +838,11 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CARGO_INCREMENTAL: "0"
|
CARGO_INCREMENTAL: "0"
|
||||||
CARGO_TARGET_DIR: ${{ github.workspace }}/target/duckdb-driver
|
CARGO_TARGET_DIR: ${{ github.workspace }}/target/duckdb-driver
|
||||||
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
|
# An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so the
|
||||||
|
# 8 MiB main-thread stack must be repeated here: without it the Windows PE
|
||||||
|
# keeps the 1 MiB linker default and deep connect/serde call stacks abort
|
||||||
|
# with 0xC00000FD ("DBX 已停止工作"). Guarded by assert-win7-pe-compat.ps1.
|
||||||
|
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static -C link-arg=/STACK:8388608
|
||||||
RUSTC_WRAPPER: sccache
|
RUSTC_WRAPPER: sccache
|
||||||
# Prefer S3 when available; GitHub's 10 GB cache quota evicts the shared
|
# Prefer S3 when available; GitHub's 10 GB cache quota evicts the shared
|
||||||
# Linux target caches when thousands of GHA sccache entries pile up.
|
# Linux target caches when thousands of GHA sccache entries pile up.
|
||||||
|
|||||||
@@ -579,7 +579,11 @@ jobs:
|
|||||||
# The vendored loader is a tracked Cargo input, so Rust compile caching is safe.
|
# The vendored loader is a tracked Cargo input, so Rust compile caching is safe.
|
||||||
env:
|
env:
|
||||||
CARGO_INCREMENTAL: "0"
|
CARGO_INCREMENTAL: "0"
|
||||||
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
|
# An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so the
|
||||||
|
# 8 MiB main-thread stack must be repeated here: without it the legacy PE
|
||||||
|
# keeps the 1 MiB linker default and deep connect/serde call stacks abort
|
||||||
|
# with 0xC00000FD ("DBX 已停止工作"). Guarded by assert-win7-pe-compat.ps1.
|
||||||
|
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static -C link-arg=/STACK:8388608
|
||||||
RUSTC_WRAPPER: sccache
|
RUSTC_WRAPPER: sccache
|
||||||
SCCACHE_GHA_ENABLED: ${{ secrets.SCCACHE_S3_BUCKET == '' && 'true' || 'false' }}
|
SCCACHE_GHA_ENABLED: ${{ secrets.SCCACHE_S3_BUCKET == '' && 'true' || 'false' }}
|
||||||
SCCACHE_GHA_VERSION: win7-webview2-1.0.902.49-v1
|
SCCACHE_GHA_VERSION: win7-webview2-1.0.902.49-v1
|
||||||
@@ -747,7 +751,11 @@ jobs:
|
|||||||
arch: x86_64
|
arch: x86_64
|
||||||
env:
|
env:
|
||||||
CARGO_INCREMENTAL: "0"
|
CARGO_INCREMENTAL: "0"
|
||||||
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
|
# An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so the
|
||||||
|
# 8 MiB main-thread stack must be repeated here: without it the legacy PE
|
||||||
|
# keeps the 1 MiB linker default and deep connect/serde call stacks abort
|
||||||
|
# with 0xC00000FD ("DBX 已停止工作"). Guarded by assert-win7-pe-compat.ps1.
|
||||||
|
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static -C link-arg=/STACK:8388608
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ jobs:
|
|||||||
timeout-minutes: 120
|
timeout-minutes: 120
|
||||||
env:
|
env:
|
||||||
CARGO_INCREMENTAL: "0"
|
CARGO_INCREMENTAL: "0"
|
||||||
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
|
# Keep the Win7/Server 2012 R2 main-thread stack at 8 MiB: an env
|
||||||
|
# RUSTFLAGS replaces the .cargo/config.toml target rustflags.
|
||||||
|
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static -C link-arg=/STACK:8388608
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
|||||||
@@ -3860,7 +3860,7 @@ fn normalized_foreign_key_action(action: Option<&str>) -> Option<String> {
|
|||||||
/// - "NO ACTION: ... For InnoDB, this is equivalent to RESTRICT".
|
/// - "NO ACTION: ... For InnoDB, this is equivalent to RESTRICT".
|
||||||
/// - "Specifying RESTRICT (or NO ACTION) is the same as omitting the ON DELETE or ON UPDATE clause."
|
/// - "Specifying RESTRICT (or NO ACTION) is the same as omitting the ON DELETE or ON UPDATE clause."
|
||||||
/// - "For an ON DELETE or ON UPDATE that is not specified, the default action is always NO ACTION."
|
/// - "For an ON DELETE or ON UPDATE that is not specified, the default action is always NO ACTION."
|
||||||
/// MariaDB KB "Foreign Keys" likewise documents `NO ACTION` as a "Synonym for RESTRICT".
|
/// MariaDB KB "Foreign Keys" likewise documents `NO ACTION` as a "Synonym for RESTRICT".
|
||||||
///
|
///
|
||||||
/// Only the comparison is normalized; generated DDL keeps the source's original action text.
|
/// Only the comparison is normalized; generated DDL keeps the source's original action text.
|
||||||
/// Known caveat: on MySQL NDB Cluster `NO ACTION` is a deferred check, but schema metadata does
|
/// Known caveat: on MySQL NDB Cluster `NO ACTION` is a deferred check, but schema metadata does
|
||||||
|
|||||||
Reference in new issue
Block a user