[binutils-gdb] gdb/Windows testsuite: Embed asInvoker manifest in test executables
Pedro Alves via Gdb-cvs <[email protected]> Wed, 22 Jul 2026 14:12:00 +0000 (GMT)
| Newsgroups | gmane.comp.gdb.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=3Dbinutils-gdb.git;h=3D33f0797fff4d= cac33092bfde19105c1eeb483526 commit 33f0797fff4dcac33092bfde19105c1eeb483526 Author: Pedro Alves <[email protected]> Date: Fri Jul 10 12:28:34 2026 +0100 gdb/Windows testsuite: Embed asInvoker manifest in test executables =20 Running gdb.base/execl-update-breakpoints.exp on Windows 11 shows this FAIL: =20 (gdb) run Starting program: .../execl-update-breakpoints1.exe Error creating process .../execl-update-breakpoints1.exe (error 740): = The requested operation requires elevation. (gdb) FAIL: gdb.base/execl-update-breakpoints.exp: runto: run to main =20 Error 740 is ERROR_ELEVATION_REQUIRED. =20 Windows has an "installer detection" heuristic that refuses to launch executables whose file name contains keywords like "update", "setup" and "install" without elevation (admin rights), unless the PE embeds an application manifest declaring requestedExecutionLevel=3D"asInvoker". =20 Some older Microsoft documentation claims the heuristic only applies to 32-bit binaries, but what I observe is that: =20 - It triggers with 64-bit PEs on current Windows. =20 And also: =20 - It matches the word (e.g. "update") as a substring anywhere in the file name, not just as a prefix. =20 - It is not drive-dependent. I thought moving the executable to a dev drive might suppress the check, but it does not. =20 I saw this problem first in a downstream ROCgdb testcase, and there I worked around it by renaming that particular testcase. This is the second case now, so rather than teach individual testcases to avoid the "bad" words, fix it once, centrally, in a way that is independent of the executable's file name. =20 The Microsoft-sanctioned escape hatch is to embed an application manifest that declares the "asInvoker" execution level. That's what this commit does, it makes gdb_compile embed one in every Windows executable it builds. =20 How the manifest gets embedded depends on the linker. There are two ways: =20 - GNU ld can't embed a manifest by itself, so compile it into a resource object with windres and link that in. =20 - lld-link is able to embed one directly, with the /manifest:embed and /manifestinput options. =20 Since it's not guaranteed that clang always links with lld-link, and conversely, gcc may also link with lld-link, gdb_compile picks between the two by probing what the linker accepts, rather than keying off the compiler or the target. =20 This whole issue only reproduces with some toolchains, because some MinGW or Cygwin installations already embed an equivalent manifest of their own, via a default-manifest.o that the gcc spec links in. That object comes from the separate windows-default-manifest package, so whether it is embedded depends on the installation rather than the gcc version. =20 Since we're adding a manifest, might as well declare the supported Windows versions there too (a compatibility section listing per-version GUIDs), like default-manifest.o does. Without those, the version-reporting APIs (GetVersionEx and friends) cap out at Windows 8. We should probably add such a manifest to GDB itself too, at some point. =20 Approved-By: Eli Zaretskii <[email protected]> Change-Id: Ic0afc925136a61c259cb8b6681627dc1775a8445 Diff: --- gdb/testsuite/lib/future.exp | 10 +++ gdb/testsuite/lib/gdb.exp | 150 +++++++++++++++++++++++++++++++++= ++++ gdb/testsuite/lib/windows.manifest | 53 +++++++++++++ gdb/testsuite/lib/windows.rc | 23 ++++++ 4 files changed, 236 insertions(+) diff --git a/gdb/testsuite/lib/future.exp b/gdb/testsuite/lib/future.exp index 0f45aa44628..3ab160a05fc 100644 --- a/gdb/testsuite/lib/future.exp +++ b/gdb/testsuite/lib/future.exp @@ -177,6 +177,16 @@ proc gdb_find_readelf {} { return $readelf } =20 +proc gdb_find_windres {} { + global WINDRES_FOR_TARGET + if {[info exists WINDRES_FOR_TARGET]} { + set windres $WINDRES_FOR_TARGET + } else { + set windres [transform windres] + } + return $windres +} + proc gdb_find_eu-unstrip {} { global EU_UNSTRIP_FOR_TARGET if {[info exists EU_UNSTRIP_FOR_TARGET]} { diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index a40c87c6727..9b86d53be08 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -4217,6 +4217,12 @@ proc is_aarch64_target {} { return [expr {![is_aarch32_target]}] } =20 +# Return true if the target is Windows-based. + +proc is_windows_based_target {} { + return [expr {[istarget *-*-cygwin*] || [istarget *-*-mingw*]}] +} + # Return 1 if displaced stepping is supported on target, otherwise, return= 0. proc support_displaced_stepping {} { =20 @@ -6402,6 +6408,97 @@ proc quote_for_host { args } { return $str } =20 +# Set while linker_supports_manifest_embed is running its test link, +# so that the inner gdb_compile that link goes through skips the +# manifest-embedding logic and doesn't recurse back into the probe. +set gdb_probing_manifest_embed 0 + +# Return the ldflags (as a list of "ldflags=3D..." options) that make +# lld-link embed our application manifest into the executable. + +proc gdb_windows_manifest_embed_lld_link_ldflags {} { + global srcdir + + set ldflags {} + + # Turn on embedding (off by default). + lappend ldflags ldflags=3D-Wl,/manifest:embed + + # Stop lld-link from also generating its own UAC block. With + # this, lld-link embeds our input as-is, while without it lld-link + # runs its manifest merger, which before LLVM 21 reprefixes + # trustInfo into the asm.v1 namespace and produces a manifest the + # Windows loader rejects. See + # <https://github.com/llvm/llvm-project/issues/120394>. + lappend ldflags ldflags=3D-Wl,/manifestuac:no + + # Embed our manifest file. Pass it in Windows-native form. + # MSYS2's argument conversion treats a "/foo:/bar" argument as a + # colon-separated list of POSIX paths and mistakenly rewrites it + # to a semicolon-separated list of Windows paths. E.g.: + # + # "/manifestinput:/c/gdb/.../windows.manifest" + # =3D> + # "C:\msys64\manifestinput;C:\gdb\...\windows.manifest" + # + # I.e., the flag name itself gets converted as if it were a path, + # and the ":" becomes ";". + # + # What triggers the conversion is the value after the colon looking + # like an absolute POSIX path (a leading "/"). "/manifest:embed" + # above is left alone because "embed" doesn't. Passing the value + # as a native "C:/..." path likewise avoids it. + set manifest [host_file_normalize ${srcdir}/lib/windows.manifest] + lappend ldflags ldflags=3D-Wl,/manifestinput:${manifest} + + return $ldflags +} + +# Compile lib/windows.rc into an object embedding our application +# manifest and return the object path, so that gdb_compile can link it +# into every Windows test executable. The result is cached. Returns +# the empty string on failure. This is used when linking with GNU ld, +# which cannot embed a manifest by itself. + +proc gdb_windows_manifest_obj {} { + global srcdir objdir + global gdb_saved_windows_manifest_obj + + if {[info exists gdb_saved_windows_manifest_obj]} { + return $gdb_saved_windows_manifest_obj + } + + set rc_src ${srcdir}/lib/windows.rc + set obj_basename windows-manifest.o + set obj [standard_temp_file $obj_basename] + + set windres [gdb_find_windres] + set cmd [list $windres -I [file dirname $rc_src] \ + -i $rc_src -o $obj -O coff] + verbose -log "Executing $cmd" + if {[catch {exec {*}$cmd} output]} { + verbose -log "gdb_windows_manifest_obj: windres failed: $output" + return "" + } + + if {[is_remote host]} { + set saved $obj_basename + } else { + set saved ${objdir}/$obj_basename + } + # Link a copy of the output object, because the original may be + # automatically deleted. + if {[info exists ::GDB_PARALLEL]} { + # Make sure to write the .o file atomically. (Note + # GDB_PARALLEL mode does not support remote host testing.) + file rename -force -- $obj $saved + } else { + remote_download host $obj $saved + } + set gdb_saved_windows_manifest_obj $saved + return $saved +} + # Compile source files specified by SOURCE into a binary of type TYPE at p= ath # DEST. gdb_compile is implemented using DejaGnu's target_compile, so the= type # parameter and most options are passed directly to it. @@ -6942,6 +7039,40 @@ proc gdb_compile {source dest type options} { } } =20 + # On Windows, embed an "asInvoker" application manifest, so that + # Windows doesn't refuse to launch executables (with + # ERROR_ELEVATION_REQUIRED/740) whose file name happens to contain + # an installer-detection keyword such as "update", "setup" or + # "install". + # + # There are two ways to get the manifest in, depending on the + # linker: + # + # - GNU ld can't embed a manifest by itself, so compile the + # manifest into a resource object with windres and link that + # in. + # + # - lld-link can embed a manifest by itself, no resource compiler + # needed. Note that the LLVM toolchain has llvm-windres and + # llvm-rc, but not all LLVM-based toolchain distributions ship + # them. + # + # Probe whether the linker supports embedding a manifest rather + # than trying to guess which linker is in use from the compiler or + # target. + if { $type =3D=3D "executable" + && [is_windows_based_target] + && !$::gdb_probing_manifest_embed } { + if { [linker_supports_manifest_embed] } { + lappend options {*}[gdb_windows_manifest_embed_lld_link_ldflags] + } else { + set manifest_obj [gdb_windows_manifest_obj] + if { $manifest_obj !=3D "" } { + lappend options "ldflags=3D$manifest_obj" + } + } + } + # Automatically handle includes in testsuite/lib/. auto_lappend_include_files options $source =20 @@ -11203,6 +11334,25 @@ gdb_caching_proc linker_supports_image_base_flag {= } { return [gdb_simple_compile $me $src executable $flags] } =20 +# Return 1 if the linker is lld-link which can embed our application +# manifest by itself, otherwise 0. Probes the exact flag combination +# gdb_compile uses. +gdb_caching_proc linker_supports_manifest_embed {} { + set me "linker_supports_manifest_embed" + set flags [gdb_windows_manifest_embed_lld_link_ldflags] + set src { int main() { return 0; } } + + # Guard against infinite recursion: the test link below itself + # goes through gdb_compile, which consults this proc to decide + # whether to embed the manifest. The guard makes that inner + # gdb_compile skip the manifest logic. + set ::gdb_probing_manifest_embed 1 + set result [gdb_simple_compile $me $src executable $flags] + set ::gdb_probing_manifest_embed 0 + + return $result +} + =20 # Return 1 if compiler supports scalar_storage_order attribute, otherwise # return 0. diff --git a/gdb/testsuite/lib/windows.manifest b/gdb/testsuite/lib/windows= .manifest new file mode 100644 index 00000000000..f0e371588f2 --- /dev/null +++ b/gdb/testsuite/lib/windows.manifest @@ -0,0 +1,53 @@ +<?xml version=3D"1.0" encoding=3D"UTF-8" standalone=3D"yes"?> +<!-- Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +--> + +<!-- Windows' installer-detection heuristic refuses to launch an + executable whose file name contains words such as "update", + "setup" or "install" unless the PE embeds an application manifest + that declares the "asInvoker" execution level. gdb_compile embeds + this manifest in every test executable: with GNU ld via the + windows.rc resource script, with lld-link via + /manifest:embed /manifestinput. See gdb_compile in lib/gdb.exp. + + The compatibility section declares the supported Windows versions. + Without it, the version-reporting APIs (GetVersionEx and friends) + cap out at Windows 8. --> +<assembly xmlns=3D"urn:schemas-microsoft-com:asm.v1" manifestVersion=3D"1.= 0"> + <trustInfo xmlns=3D"urn:schemas-microsoft-com:asm.v3"> + <security> + <requestedPrivileges> + <requestedExecutionLevel level=3D"asInvoker" uiAccess=3D"false"/> + </requestedPrivileges> + </security> + </trustInfo> + <compatibility xmlns=3D"urn:schemas-microsoft-com:compatibility.v1"> + <application> + <!-- Windows Vista --> + <supportedOS Id=3D"{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> + <!-- Windows 7 --> + <supportedOS Id=3D"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + <!-- Windows 8 --> + <supportedOS Id=3D"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + <!-- Windows 8.1 --> + <supportedOS Id=3D"{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <!-- Windows 10 and Windows 11 --> + <supportedOS Id=3D"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + </application> + </compatibility> +</assembly> diff --git a/gdb/testsuite/lib/windows.rc b/gdb/testsuite/lib/windows.rc new file mode 100644 index 00000000000..2c1b2ca81d4 --- /dev/null +++ b/gdb/testsuite/lib/windows.rc @@ -0,0 +1,23 @@ +/* Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. = */ + +/* Embed the application manifest. + + 1 =3D> CREATEPROCESS_MANIFEST_RESOURCE_ID + 24 =3D> RT_MANIFEST +*/ +1 24 "windows.manifest"