public inbox for gcc@gcc.gnu.org
 help / color / mirror / Atom feed
From: "Pali Rohár" <pali.rohar@gmail.com>
To: binutils@sourceware.org, gcc@gcc.gnu.org,
	mingw-w64-public@lists.sourceforge.net
Subject: Problems when building NT kernel drivers with GCC / LD
Date: Sun, 30 Oct 2022 02:06:11 +0200	[thread overview]
Message-ID: <20221030000611.ytfadi4f2xcvodey@pali> (raw)

Hello!

I tried to develop simple NT kernel driver and build it via GNU GCC, LD
and mingw-w64 ecosystem but due to issues with compiler and linker, it
is not possible to generate final .sys binary without adding workarounds
or postprocessing resulted PE .sys binary.

I'm sending this email to all parties (binutils = linker, gcc = compiler,
mingw-w64 = runtime) which are involved in building process of NT kernel
driver (which is just native PE binary) and I would like to describe
existing issues and ask if you can come up with fixes.

For demonstration I created very simple NT kernel driver which can be
compiled by MSVC compiler without any issues, so you can directly look
at the code and play with it: https://github.com/pali/secure-giveio
I have there also GNUmakefile which uses GNU tools for compilation and
tries to workaround issues.

Here is the list of those GCC / LD issues which I observed during
development and compilation of NT kernel drivers:

* GCC does not support __declspec(code_seg("segname")) declarator for
  specifying name of PE/COFF segment name. But instead GCC supports
  different syntax __declspec(section("segname")) for this thing.

  I thought that GCC added __declspec support for compatibility with
  other PE/COFF compilers. But because declarators are different it just
  means that __declspec segment name is not compatible neither with MSVC.

  If the GCC's intention of __declspec is compatibility support then it
  would be really really useful to have support for __declspec(code_seg
  instead of custom __declspec(section syntax supported only by GCC.

  As a workaround to have driver compilable by both MSVC and GCC it is
  needed to add #ifdef for GCC compiler.

* LD --dynamicbase is not working correctly. If used for PE executables
  (not dynamic libraries) then it does *not* generate relocation info.
  And without relocation info in PE binary, it is not possible to
  relocate base address. Which makes dynamic base non-working.

  As a workaround GCC -pie can be used to generate relocation info (but
  it is buggy too, see below).

* GCC -pie is unusable. It automatically generates export symbol table
  even when PE binary must not export anything. This applies for Native
  PE executables used as NT kernel .sys drivers. Normally only dynamic
  libraries export symbols, so by default for executables should not
  symbol table. Or at least there should be a flag which can disable
  doing it.

  I tried to workaround it via LD --exclude-all-symbols, but it is also
  not usable, see below.

* LD --exclude-all-symbols has issues for Native PE executables. It
  causes generation of empty export table and puts name of the output
  executable/driver (-o) as name of the export library. PE executables
  which are in shared address space (e.g. NT driver executables) should
  really do not be treated as libraries with library name (unless
  explicitly asked for such thing).

  As a workaround I had to create a custom linker script which discards
  that nonsense export table by completely dropping .edata section.

* LD puts discardable init sections in the middle of the PE binary. This
  is really stupid as loaded PE binary waste memory space. Discardable
  init sections should be at the end of the PE binary.

  As a workaround I used custom linker script.

* GCC or LD (not sure who) sets memory alignment characteristics
  (IMAGE_SCN_ALIGN_MASK) into the sections of PE executable binary.
  These characteristics should be only in COFF object files, not
  executable binaries. Specially they should not be in NT kernel
  drivers.

* GCC or LD (not sure who) sets data characteristics
  (IMAGE_SCN_CNT_INITIALIZED_DATA) for executable PE code sections.
  Code sections should not be really marked as data, only as a code
  (IMAGE_SCN_CNT_CODE).

* GCC or LD (not sure who) sets memory writable characteristics
  (IMAGE_SCN_MEM_WRITE) into the read-only resources PE sections.
  Read-only sections of final PE executable really should not marked as
  writable.

* GCC or LD (not sure who) does *not* set memory discardable
  characteristic (IMAGE_SCN_MEM_DISCARDABLE) for sections used only
  during initial phase in NT kernel driver PE executables. This applies
  for .idata, INIT and .rsrc sections. Without that characteristic there
  is wasting of kernel memory.

* GCC or LD (not sure who) does *not* set memory not_paged
  characteristics (IMAGE_SCN_MEM_NOT_PAGED) for PE sections which must
  not be paged in NT kernel driver executables. Basically most sections
  should must not be paged unless explicitly asked/marked. By default
  just section with name PAGE could be paged and therefore only this
  section does not have to have IMAGE_SCN_MEM_NOT_PAGED.

Last five issues I was not able to workaround neither via gcc or ld
flags and neither via linker script. For example MSVC link.exe linker
has switch /SECTION: which can be used to specify above characteristics
specific section. But whatever I tried to do in GNU linker script, I was
not able to set and fix those characteristics. For this reason I wrote
custom application which post-process built PE executable and fix
section characteristics, which GNU GCC/LD generated incorrectly.

Then I have there rather small but sometimes annoying objcopy issue.
objcopy does not respect --disable-deterministic-archives or
--preserve-dates switches and always deletes PE date/timestamp from
executable. I was not able to find any switch which can tell objcopy to
preserve PE date/timestamp. LD has flag --insert-timestamp for this
thing, and this LD switch is working.


Could you please look at those issues? Is there something which can be
done to fix them? Because it is really stop-blocker for building even
simple NT kernel drivers via open source GNU tools. Basically one main
problem - generate relocation info for native executable - just reveal
chain of other issues.

NT kernel .sys drivers are just simple Native PE executables (like
ordinary .exe executables) but with relocation info (like .dll
libraries) with fixed entry point, linked to ntoskrnl and have some
characteristics.

Following gcc flags produce such executable (but with above issues):

  -Wl,--subsystem,native
  -Wl,--dynamicbase -Wl,--nxcompat
  -Wl,--image-base,0x10000 -Wl,--stack,0x40000
  -Wl,--section-alignment,0x1000 -Wl,--file-alignment,0x200
  -Wl,--disable-auto-import -Wl,--disable-stdcall-fixup
  -nostartfiles -nodefaultlibs -nostdlib
  -lntoskrnl

Note that entry point is always stdcall "DriverEntry" function. And
because of symbol mangling, its symbol name is "_DriverEntry@8" for IX86
and "DriverEntry" for AMD64. LD switch --entry= takes symbol name, not
function name. So it would be a nice feature to allow specifying entry
point via GCC/LD also as function name (without mangling) to have just

  -Wl,--entry=DriverEntry

parameter for gcc for all 32-bit and 64-bit builds.

And also it would be a nice to have some GCC flag which sets all above
flags required for compiling NT kernel driver. Like there is -mdll for
compiling DLL libraries or -mwindows for generating GUI WIN32
executables. For example MSVC link.exe has for this switch /DRIVER.

If you have some questions then please let me know. I think that it
would be really useful if GNU tools would be able to build NT kernel
drivers without needs for hacks.

             reply	other threads:[~2022-10-30  0:06 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-10-30  0:06 Pali Rohár [this message]
2022-10-30  7:06 ` [Mingw-w64-public] " Martin Storsjö
2022-10-30 19:58   ` ralph engels
2022-10-31  9:55 ` Jan Beulich
2022-11-05  0:57   ` Pali Rohár
2022-11-05  1:26     ` Pali Rohár
2022-11-20 13:10       ` Pali Rohár
2022-11-21  7:24         ` Jan Beulich
2022-11-26 19:04           ` Pali Rohár
2022-11-28  8:07             ` Jan Beulich
2022-11-28  8:40               ` Jonathan Wakely
2022-11-28  9:06                 ` Jan Beulich
2022-12-26 10:47 ` Pali Rohár
2023-01-03 11:06   ` Nick Clifton
2023-02-20 18:25     ` Pali Rohár
2023-04-01  9:23       ` Pali Rohár
2023-04-12  9:53         ` Nick Clifton
2023-04-12 20:40           ` Pali Rohár
2024-01-07  1:55       ` Pali Rohár
2024-04-07 23:20         ` Pali Rohár

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20221030000611.ytfadi4f2xcvodey@pali \
    --to=pali.rohar@gmail.com \
    --cc=binutils@sourceware.org \
    --cc=gcc@gcc.gnu.org \
    --cc=mingw-w64-public@lists.sourceforge.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).