From 188aeb2923283620ba772e37c6c4d6a7cb605b78 Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Thu, 17 Nov 2022 21:55:34 -0500 Subject: [PATCH] Add a macro to probe -mwindows on MinGW. MinGW is typically configured to build console applications by default. So in order to build a normal Windows GUI application, this flag is needed when linking. Otherwise, a console window will be opened when the program is run. Other Windows compilers probably have similar options which can added to the probe in the future. --- m4/w32gui.m4 | 32 ++++++++++ scripts/pe-subsys.awk | 145 ++++++++++++++++++++++++++++++++++++++++++ t/ccw32.sh | 47 ++++++++++++++ tests/macros.at | 55 ++++++++++++++++ 4 files changed, 279 insertions(+) create mode 100644 m4/w32gui.m4 create mode 100755 scripts/pe-subsys.awk create mode 100755 t/ccw32.sh diff --git a/m4/w32gui.m4 b/m4/w32gui.m4 new file mode 100644 index 0000000..f333015 --- /dev/null +++ b/m4/w32gui.m4 @@ -0,0 +1,32 @@ +dnl Copyright © 2022 Nick Bowler +dnl +dnl License WTFPL2: Do What The Fuck You Want To Public License, version 2. +dnl This is free software: you are free to do what the fuck you want to. +dnl There is NO WARRANTY, to the extent permitted by law. + +dnl DX_W32_GUI +dnl +dnl Determine the C linker options needed to build a Windows graphical +dnl application (i.e., for the "windows" subsystem). The result is saved +dnl in the cache variable dx_cv_w32_gui_flags. +dnl +dnl If no suitable flags could be determined, the cache variable dx_cv_w32_gui +dnl is set to "unknown" and dx_cv_w32_gui_flags is set to the empty string. + +AC_DEFUN([DX_W32_GUI], [AC_REQUIRE([AC_PROG_AWK])dnl +AC_CACHE_CHECK([for $CC option to target W32 GUI], [dx_cv_w32_gui], +[_dx_check_pe_subsys () { + $AWK -f m4_do([m4_pushdef([m4_include], [$][1])], + [m4_include(DX_BASEDIR[/scripts/pe-subsys.awk])], + [m4_popdef([m4_include])]) "conftest$EXEEXT" +} +dx_cv_w32_gui=unknown _dx_save_LDFLAGS=$LDFLAGS +for dx_cv_w32_gui_flags in '' -mwindows; do +LDFLAGS="$_dx_save_LDFLAGS $dx_cv_w32_gui_flags" +AC_LINK_IFELSE([AC_LANG_PROGRAM], +[AS_CASE([`_dx_check_pe_subsys`], +[gui], [dx_cv_w32_gui=${dx_cv_w32_gui_flags:-none needed}; break] +)]) +done +test x"$dx_cv_w32_gui" != x"unknown" || dx_cv_w32_gui_flags= +LDFLAGS=$_dx_save_LDFLAGS])]) diff --git a/scripts/pe-subsys.awk b/scripts/pe-subsys.awk new file mode 100755 index 0000000..62e9057 --- /dev/null +++ b/scripts/pe-subsys.awk @@ -0,0 +1,145 @@ +#!/bin/awk -f +# +# Determine the subsystem of a PE32 executable. +# +# Copyright © 2022 Nick Bowler +# +# License WTFPL2: Do What The Fuck You Want To Public License, version 2. +# This is free software: you are free to do what the fuck you want to. +# There is NO WARRANTY, to the extent permitted by law. + +BEGIN { + filename = ARGV[1]; + subsys = "unknown"; + endian = "unknown"; + + if (!filename) { + exit(1); + } + probe_dumper(filename); + + od_read("0"); + if ($2 == "055115") { + endian = "little"; + } else if ($2 == "046532") { + endian = "big"; + } else { + exit(1); + } + + od_read("74"); + val = 65536*od_decode($3) + od_decode($2); + + pe_offset = sprintf("%o", val); + pe32_offset = sprintf("%o", val+24); + subsys_offset = sprintf("%o", val+24+68); + + od_read(pe_offset); + if ($2 != "042520" || $3 != "000000") { + # bad PE header + exit(1); + } + + od_read(pe32_offset); + if ($2 != "000413" && $2 != "001013") { + # bad PE32(+) header + exit(1); + } + + od_read(subsys_offset); + subsys = od_decode($2); + if (subsys == 2) + subsys = "gui"; + else if (subsys == 3) + subsys = "console"; + else + subsys = "unknown (" subsys ")"; + + exit(0); +} + +END { + print subsys +} + +function probe_dumper(filename) +{ + (cmd = "od " filename " +10 2>/dev/null") | getline + close(cmd) + + sub(/^0*/, "", $1); + if ($1 == 10) { + dumper_style = "od_traditional"; + return; + } + + (cmd = "od -j010 " filename " 2>/dev/null") | getline + close(cmd) + + sub(/^0*/, "", $1); + if ($1 == 10) { + dumper_style = "od_posix"; + return; + } + + exit(1); +} + +function od_read(offset, cmd) +{ + if (dumper_style == "od_traditional") { + cmd = "od " filename " +" offset; + } else if (dumper_style == "od_posix") { + cmd = "od -j0" offset " " filename; + } + + cmd | getline + close(cmd); + + if (endian == "big") { + $2 = od_bswap($2); + $3 = od_bswap($3); + } +} + +# Byte-swap one of the 2-byte blocks of octal digits emitted by od. +function od_bswap(word, i, digits, carry) +{ + for (i = 0; i < 6; i++) { + digits[i] = substr(word, i+1, 1); + } + + # suss out the first byte by adjusting the first 4 digits left by 1 bit + if (carry = (digits[3] >= 4)) + digits[3] -= 4; + + for (i = 2; i >= 0; i--) { + if (carry = ( (digits[i] = digits[i]*2 + carry) >= 8 )) + digits[i] -= 8; + } + + # now munge the second byte by adjusting the last 3 digits right 1 bit + carry = 0; + for (i = 3; i < 6; i++) { + carry = (digits[i] += 8*carry) % 2; + digits[i] = int(digits[i] / 2); + } + + # and put the leftover bit in place + digits[0] += 4*carry; + + return digits[3] digits[4] digits[5] digits[0] digits[1] digits[2]; +} + +# Parse a string of octal digits into a number. +function od_decode(word, result) +{ + result = 0; + + while (word) { + result = 8*result + substr(word, 1, 1); + word = substr(word, 2); + } + + return result; +} diff --git a/t/ccw32.sh b/t/ccw32.sh new file mode 100755 index 0000000..9a0fe7c --- /dev/null +++ b/t/ccw32.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# +# Copyright © 2022 Nick Bowler +# +# Fake C compiler for testing PE subsystem probes. +# +# License WTFPL2: Do What The Fuck You Want To Public License, version 2. +# This is free software: you are free to do what the fuck you want to. +# There is NO WARRANTY, to the extent permitted by law. + +mode=link +outfile= +infile= + +: "${TEST_SUBSYS=3}" +: "${TEST_GUI_FLAG=-mwindows}" + +for arg; do + shift + + case $arg in + -o) outfile=$1; ;; + -c) mode=compile ;; + *.c) infile=$arg ;; + $TEST_GUI_FLAG) TEST_SUBSYS=2 ;; + esac +done + +test x"$infile" != x || exit +filebase=`expr "$infile" : '\(.*\).c$'` + +case $mode in +link) + : "${outfile:=a.exe}" + ;; +compile) + : "${outfile:=$filebase.obj}" + ;; +esac + +:; { + printf '%-60s' MZ + printf '\101\1\0\0%257s' '' + printf 'PE\0\0%20s' '' + printf '\13\1%66s' '' + printf "\\$TEST_SUBSYS\\0" +} >"$outfile" diff --git a/tests/macros.at b/tests/macros.at index 99b9351..a085ad6 100644 --- a/tests/macros.at +++ b/tests/macros.at @@ -542,3 +542,58 @@ AT_CHECK([$SHELL exported.sh helpopt.lo], [0], [expout]) AT_CHECK([$SHELL exported.sh nonexistent], [1], [], [ignore]) AT_CLEANUP + +AT_SETUP([DX_W32_GUI]) + +AT_DATA([test.in], [[@dx_cv_w32_gui@ +@dx_cv_w32_gui_flags@ +]]) + +TEST_CONFIGURE_AC([[DX_W32_GUI +AC_SUBST([dx_cv_w32_gui]) +AC_SUBST([dx_cv_w32_gui_flags]) +AC_CONFIG_FILES([test]) +]]) +TEST_AUTORECONF + +myconf="cross_compiling=yes --host=none CC=$srcdir/t/ccw32.sh" + +TEST_CONFIGURE([TEST_SUBSYS=2 TEST_GUI_FLAG=-mwindows $myconf]) +AT_CHECK([cat test], [0], [none needed + +]) + +TEST_CONFIGURE([TEST_SUBSYS=3 TEST_GUI_FLAG=-mwindows $myconf]) +AT_CHECK([cat test], [0], [-mwindows +-mwindows +]) + +TEST_CONFIGURE([TEST_SUBSYS=4 TEST_GUI_FLAG=xxx $myconf]) +AT_CHECK([cat test], [0], [unknown + +]) + +AT_CLEANUP + +AT_SETUP([DX_W32_GUI distribution]) +AT_KEYWORDS([DX_W32_GUI macro]) + +TEST_CONFIGURE_AC([[AM_INIT_AUTOMAKE([foreign]) +DX_W32_GUI +AC_CONFIG_FILES([Makefile]) +]]) + +AT_DATA([Makefile.am], +[[foo: ; printf '%s\n' $(DX_BASEDIR) $(DISTFILES) +]]) +TEST_AUTORECONF + +TEST_CONFIGURE +AT_CHECK([make -s foo], [0], [stdout]) +AT_CHECK([exec 3