From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from list by lists.gnu.org with archive (Exim 4.90_1) id 1q9gN4-0003l6-QQ for mharc-grub-devel@gnu.org; Thu, 15 Jun 2023 02:22:14 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1q9gN2-0003kv-QJ for grub-devel@gnu.org; Thu, 15 Jun 2023 02:22:13 -0400 Received: from mail-lf1-x12a.google.com ([2a00:1450:4864:20::12a]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1q9gMx-0001Tc-5R for grub-devel@gnu.org; Thu, 15 Jun 2023 02:22:10 -0400 Received: by mail-lf1-x12a.google.com with SMTP id 2adb3069b0e04-4f640e48bc3so9777010e87.2 for ; Wed, 14 Jun 2023 23:22:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1686810125; x=1689402125; h=in-reply-to:content-disposition:mime-version:references :mail-followup-to:message-id:subject:cc:to:from:date:from:to:cc :subject:date:message-id:reply-to; bh=n5EdFkLuDjwk6DEOvO/RwoDMtEAblE622WRfI4SyyaE=; b=aDpS9/t73htVuOfYOT/i0edEEQuz3cLw5QTeVLUia4A/AGaAtw95MERnl8LpZXeHQ1 VmwNFNfz/jAV+dh8AkEXKgbdHK3XFIX3b4QYspa0UtslrDd894mfC1Jg01S7TBSslLy7 /lArIp/7qpmj+rwL0CgqPLbA23WGfqcL+ZS1SJD42nz3z19lNH2pAXUgTreVg6n4o5OJ z75IzSX/6GZKmabC2fUHmejeBXr04+XPsBWMY/F+/gpkTX0NNRcOq84u73brbnN4KaMg IfXo4bz51glv/aShJYhWTDTLa9QDADAWVZHI6vK0RPqsfLy2lFftkRxfmAJEl+KdwE35 7A7Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1686810125; x=1689402125; h=in-reply-to:content-disposition:mime-version:references :mail-followup-to:message-id:subject:cc:to:from:date :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=n5EdFkLuDjwk6DEOvO/RwoDMtEAblE622WRfI4SyyaE=; b=kKm/O2tM3khBlMrC6H/YLL8IvwGTxocs67dYPx/gyGnzMZYhEPBTXCyaM4AvN2SPfg yL/eLgkg9UTynNBs3eORBN0b/22MzRp5eMzjBxsukIb+hY09R6s4CXJXbow8kKNtMPOG +TCs9O8EVHac5ZERQCyuzqyKc1QwUNai6wVY4KsYYryS91lwsLlaef3yi8QWOCvi+30U cdDPS98XlP5zjDsB9cZMapgnAG8CuAwrydRoC2TnRHebmcyM0ko11/Cl2DZSQ9q5OMh8 Jw1+fmhwS/qlkHAQUMzJ+vhyw0DFO2tBXnjWVb0n988b+KE5AAX0YJaVbbzcN4uij6Ww s97g== X-Gm-Message-State: AC+VfDzyuknN2wiIXuZj8AFWzDGURMv3v7uZk3ryGAIvRusBATM+hS61 oM8vjEXUzMEg1S25YTgmkFtftZQSqZ4= X-Google-Smtp-Source: ACHHUZ5qrJdSk49HAkMj4VsderZ7jWTNKamnuB3+jcns/N7cb5MAF4Nmm7ylSF8i+D+2Z/X8Kyzitw== X-Received: by 2002:a19:3854:0:b0:4f6:16f9:a037 with SMTP id d20-20020a193854000000b004f616f9a037mr8149827lfj.69.1686810124132; Wed, 14 Jun 2023 23:22:04 -0700 (PDT) Received: from dj3ntoo (220.sub-97-147-16.myvzw.com. [97.147.16.220]) by smtp.gmail.com with ESMTPSA id z13-20020ac25ded000000b004f4589808ddsm2413867lfq.305.2023.06.14.23.22.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 Jun 2023 23:22:03 -0700 (PDT) Date: Thu, 15 Jun 2023 01:21:57 -0500 From: Oskari Pirhonen To: Glenn Washburn Cc: grub-devel@gnu.org, Daniel Kiper Subject: Re: [PATCH v3] docs: Add debugging chapter to development documentation Message-ID: Mail-Followup-To: Glenn Washburn , grub-devel@gnu.org, Daniel Kiper References: <20230606054839.212197-1-development@efficientek.com> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha256; protocol="application/pgp-signature"; boundary="XzSEr49fHCefsqCN" Content-Disposition: inline In-Reply-To: <20230606054839.212197-1-development@efficientek.com> Received-SPF: pass client-ip=2a00:1450:4864:20::12a; envelope-from=xxc3ncoredxx@gmail.com; helo=mail-lf1-x12a.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: grub-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: The development of GNU GRUB List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 15 Jun 2023 06:22:13 -0000 --XzSEr49fHCefsqCN Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable Oops, apologies for the late reply. Reading through it again, I found a few more small nits: On Tue, Jun 06, 2023 at 00:48:39 -0500, Glenn Washburn wrote: > Debugging GRUB can be tricky and require arcane knowledge. This will > help those unfamiliar with the process to get started debugging GRUB > with less effort. >=20 > Signed-off-by: Glenn Washburn > --- > Changes from v1: > * Add gdbinfo section > --- > Interdiff against v2: > diff --git a/docs/grub-dev.texi b/docs/grub-dev.texi > index 188ca9c7ca6e..72470b42c61a 100644 > --- a/docs/grub-dev.texi > +++ b/docs/grub-dev.texi > @@ -638,7 +638,7 @@ various targets using @command{gdb} and the @samp{g= db_grub} GDB script. > @section i386-pc > =20 > The i386-pc target is a good place to start when first debugging GRUB2 > -because in some respects its easier than EFI platforms. The reason bei= ng > +because in some respects it's easier than EFI platforms. The reason be= ing > that the initial load address is always known in advance. To start > debugging GRUB2 first QEMU must be started in GDB stub mode. The follo= wing > command is a simple illustration: > @@ -688,11 +688,11 @@ it does add the module symbols with the appropria= te offset. > @section x86_64-efi > =20 > Using GDB to debug GRUB2 for the x86_64-efi target has some similariti= es with > -the i386-pc target. Please read be familiar with the @ref{i386-pc} sec= tion > -when reading this one. Extra care must be used to run QEMU such that i= t boots > -a UEFI firmware. This usually involves either using the @samp{-bios} o= ption > -with a UEFI firmware blob (eg. @file{OVMF.fd}) or loading the firmware= via > -pflash. This document will not go further into how to do this as there= are > +the i386-pc target. Please read and familiarize yourself with the @ref= {i386-pc} > +section when reading this one. Extra care must be used to run QEMU suc= h that it > +boots a UEFI firmware. This usually involves either using the @samp{-b= ios} > +option with a UEFI firmware blob (eg. @file{OVMF.fd}) or loading the f= irmware > +via pflash. This document will not go further into how to do this as t= here are > ample resource on the web. > =20 > Like all EFI implementations, on x86_64-efi the (U)EFI firmware that l= oads > @@ -700,7 +700,7 @@ the GRUB2 EFI application determines at runtime whe= re the application will > be loaded. This means that we do not know where to tell GDB to load the > symbols for the GRUB2 core until the (U)EFI firmware determines it. Th= ere are > two good ways of figuring this out when running in QEMU: use a @ref{OV= MF debug log, > -debug build of OVMF} and check the debug log or have GRUB2 say where i= t is > +debug build of OVMF} and check the debug log, or have GRUB2 say where = it is > loaded. Neither of these are ideal because they both generally give the > information after GRUB2 is already running, which makes debugging earl= y boot > infeasible. Technically, the first method does give the load address b= efore > @@ -734,11 +734,11 @@ application must be run via QEMU at least once pr= ior in order to get the > load address. Two methods for obtaining the load address are described= in > two subsections below. Generally speaking, the load address does not c= hange > between QEMU runs. There are exceptions to this, namely that different > -GRUB2 EFI applications can be run at different addresses. Also, its be= en > +GRUB2 EFI applications can be run at different addresses. Also, it has= been > observed that after running the EFI application for the first time, the > second run will some times have a different load address, but subseque= nt > runs of the same EFI application will have the same load address as the > -second run. And its a near certainty that if the GRUB EFI binary has c= hanged, > +second run. And it's a near certainty that if the GRUB EFI binary has = changed, > eg. been recompiled, the load address will also be different. > =20 > This ability to predict what the load address will be allows one to as= sume > @@ -752,7 +752,7 @@ gdb -x gdb_grub -ex 'dynamic_load_symbols @var{addr= ess of .text section}' > @end example > =20 > If you load the symbols in this manner and, after continuing execution= , do > -not see output showing the loading of modules symbol, then its very li= kely > +not see output showing the loading of modules symbol, then it is very = likely > that the load address was incorrect. > =20 > Another thing to be aware of is how the loading of the GRUB image by t= he > @@ -760,8 +760,8 @@ firmware affects previously set software breakpoint= s. On x86 platforms, > software breakpoints are implemented by GDB by writing a special proce= ssor > instruction at the location of the desired breakpoint. This special in= struction > when executed will stop the program execution and hand control to the > -debugger, GDB. GDB will first saves the instruction bytes that will be > -overwritten at the breakpoint, and will put them back when the breakpo= int > +debugger, GDB. GDB will first save the instruction bytes that are > +overwritten at the breakpoint and will put them back when the breakpoi= nt > is hit. If GRUB is being run for the first time in QEMU, the firmware = will > be loading the GRUB image into memory where every byte is already set = to 0. > This means that if a breakpoint is set before GRUB is loaded, GDB will= save >=20 > docs/grub-dev.texi | 224 +++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 224 insertions(+) >=20 > diff --git a/docs/grub-dev.texi b/docs/grub-dev.texi > index 31eb99ea2994..72470b42c61a 100644 > --- a/docs/grub-dev.texi > +++ b/docs/grub-dev.texi > @@ -79,6 +79,7 @@ This edition documents version @value{VERSION}. > * Contributing Changes:: > * Setting up and running test suite:: > * Updating External Code:: > +* Debugging:: > * Porting:: > * Error Handling:: > * Stack and heap size:: > @@ -595,6 +596,229 @@ cp minilzo-2.10/*.[hc] grub-core/lib/minilzo > rm -r minilzo-2.10* > @end example > =20 > +@node Debugging > +@chapter Debugging > + > +GRUB2 can be difficult to debug because it runs on the bare-metal and th= us > +does not have the debugging facilities normally provided by an operating > +system. This chapter aims to provide useful information on some ways to > +debug GRUB2 for some architectures. It by no means intends to be exhaust= ive. > +The focus will be one x86_64 and i386 architectures. Luckily for some is= sues > +virtual machines have made the ability to debug GRUB2 much easier, and t= his > +chapter will focus debugging via the QEMU virtual machine. We will not be > +going over debugging of the userland tools (eg. grub-install), there are > +many tutorials on debugging programs in userland. > + > +You will need GDB and the QEMU binaries for your system, on Debian these > +can be installed with the @samp{gdb} and @samp{qemu-system-x86} packages. > +Also it is assumed that you have already successfully compiled GRUB2 from > +source for the target specified in the section below and have some > +familiarity with GDB. When GRUB2 is built it will create many different > +binaries. The ones of concern will be in the @file{grub-core} > +directory of the GRUB2 build dir. To aide in debugging we will want the > +debugging symbols generated during the build because these symbols are n= ot > +kept in the binaries which get installed to the boot location. The build > +process outputs two sets of binaries, one without symbols which gets exe= cuted > +at boot, and another set of ELF images with debugging symbols. The built > +images with debugging symbols will have a @file{.image} suffix, and the = ones > +without a @file{.img} suffix. Similarly, loadable modules with debugging > +symbols will have a @file{.module} suffix, and ones without a @file{.mod} > +suffix. In the case of the kernel the binary with symbols is named > +@file{kernel.exec}. > + > +In the following sections, information will be provided on debugging on > +various targets using @command{gdb} and the @samp{gdb_grub} GDB script. > + > +@menu > +* i386-pc:: > +* x86_64-efi:: > +@end menu > + > +@node i386-pc > +@section i386-pc > + > +The i386-pc target is a good place to start when first debugging GRUB2 > +because in some respects it's easier than EFI platforms. The reason being > +that the initial load address is always known in advance. To start > +debugging GRUB2 first QEMU must be started in GDB stub mode. The followi= ng > +command is a simple illustration: > + > +@example > +qemu-system-i386 -drive file=3Ddisk.img,format=3Draw \ > + -device virtio-scsi-pci,id=3Dscsi0 -S -s > +@end example > + > +This will start a QEMU instance booting from @file{disk.img}. It will pa= use > +at start waiting for a GDB instance to attach to it. You should change > +@file{disk.img} to something more appropriate. A block device can be use= d, > +but you may need to run QEMU as a privileged user. > + > +To connect to this QEMU instance with GDB, the @code{target remote} GDB > +command must be used. We also need to load a binary image, preferably wi= th > +symbols. This can be done using the GDB command @code{file kernel.exec},= if > +GDB is started from the @file{grub-core} directory in the GRUB2 build > +directory. GRUB2 developers have made this more simple by including a GDB > +script which does much of the setup. This file at @file{grub-core/gdb_gr= ub} > +of the build directory and is also installed via @command{make install}. This sentence is definitely missing an "is" or similar, but I'd write something like: This file is at grub-core/gdb_grub in the build directory > +If not building GRUB, the distribution may have a package which installs > +this GDB script along with debug symbol binaries, such as Debian's > +@samp{grub-pc-dbg} package. The GDB scripts is intended to by used If it's just a single script, this should be: The GDB script is intended to by used > +like so, assuming: Did you forget to state what the assumption is? > + > +@example > +cd $(dirname /path/to/script/gdb_grub) > +gdb -x gdb_grub > +@end example > + > +Once GDB has been started with the @file{gdb_grub} script it will > +automatically connect to the QEMU instance. You can then do things you > +normally would in GDB like set a break point on @var{grub_main}. > + > +Setting breakpoints in modules is trickier since they haven't been loaded > +yet and are loaded at addresses determined at runtime. The module could = be > +loaded to different addresses in different QEMU instances. The debug sym= bols > +in the modules @file{.module} binary, thus are always wrong, and GDB nee= ds > +to be told where to load the symbols to. But this must happen at runtime > +after GRUB2 has determined where the module will get loaded. Luckily the > +@file{gdb_grub} script takes care of this with the @command{runtime_load= _module} > +command, which configures GDB to watch for GRUB2 module loading and when > +it does add the module symbols with the appropriate offset. > + > +@node x86_64-efi > +@section x86_64-efi > + > +Using GDB to debug GRUB2 for the x86_64-efi target has some similarities= with > +the i386-pc target. Please read and familiarize yourself with the @ref{i= 386-pc} > +section when reading this one. Extra care must be used to run QEMU such = that it > +boots a UEFI firmware. This usually involves either using the @samp{-bio= s} > +option with a UEFI firmware blob (eg. @file{OVMF.fd}) or loading the fir= mware > +via pflash. This document will not go further into how to do this as the= re are > +ample resource on the web. > + > +Like all EFI implementations, on x86_64-efi the (U)EFI firmware that loa= ds > +the GRUB2 EFI application determines at runtime where the application wi= ll > +be loaded. This means that we do not know where to tell GDB to load the > +symbols for the GRUB2 core until the (U)EFI firmware determines it. Ther= e are > +two good ways of figuring this out when running in QEMU: use a @ref{OVMF= debug log, > +debug build of OVMF} and check the debug log, or have GRUB2 say where it= is > +loaded. Neither of these are ideal because they both generally give the > +information after GRUB2 is already running, which makes debugging early = boot > +infeasible. Technically, the first method does give the load address bef= ore > +GRUB2 is run, but without debugging the EFI firmware with symbols, the a= uthor > +currently does not know how to cause the OVMF firmware to pause at that = point > +to use the load address before GRUB2 is run. > + > +Even after getting the application load address, the loading of core sym= bols > +is complicated by the fact that the debugging symbols for the kernel are= in > +an ELF binary named @file{kernel.exec} while what is in memory are secti= ons > +for the PE32+ EFI binary. When @command{grub-mkimage} creates the PE32+ > +binary it condenses several segments from the ELF kernel binary into one > +.data section in the PE32+ binary. This must be taken into account to > +properly load the other non-text sections. Otherwise, GDB will work as > +expected when breaking on functions, but, for instance, global variables > +will point to the wrong address in memory and thus give incorrect values > +(which can be difficult to debug). > + > +The calculating of the correct offsets for sections when loading symbol > +files are taken care of when loading the kernel symbols via the user-def= ined This sentence feels a bit clumsy. I'd write something like: Calculating the correct offsets for sections is taken care of automatically when loading the kernel symbols via the user-defined... I was originally going to suggest "section offsets" here too, but I'm not confident that it couldn't potentially mean something else in this context. > +GDB command @command{dynamic_load_kernel_exec_symbols}, which takes one > +argument, the address where the text section is loaded, as determined by I would personally drop the second comma in "argument, ... as determined by". > +one of the methods above. Alternatively, the command @command{dynamic_lo= ad_symbols} > +with the text section address as an agrument can be called to load the > +kernel symbols and setup loading the module symbols as they are loaded at "setup" should probably be "set up". > +runtime. > + > +In the author's experience, when debugging with QEMU and OVMF, to have > +debugging symbols loaded at the start of GRUB2 execution the GRUB2 EFI > +application must be run via QEMU at least once prior in order to get the > +load address. Two methods for obtaining the load address are described in > +two subsections below. Generally speaking, the load address does not cha= nge > +between QEMU runs. There are exceptions to this, namely that different > +GRUB2 EFI applications can be run at different addresses. Also, it has b= een > +observed that after running the EFI application for the first time, the > +second run will some times have a different load address, but subsequent "some times" should probably be "sometimes". > +runs of the same EFI application will have the same load address as the > +second run. And it's a near certainty that if the GRUB EFI binary has ch= anged, > +eg. been recompiled, the load address will also be different. > + > +This ability to predict what the load address will be allows one to assu= me > +the load address on subsequent runs and thus load the symbols before GRU= B2 > +starts. The following command illustrates this, assuming that QEMU is > +running and waiting for a debugger connection and the current working > +directory is where @file{gdb_grub} resides: > + > +@example > +gdb -x gdb_grub -ex 'dynamic_load_symbols @var{address of .text section}' > +@end example > + > +If you load the symbols in this manner and, after continuing execution, = do > +not see output showing the loading of modules symbol, then it is very li= kely Would this make more sense as "showing the module symbols loading"? > +that the load address was incorrect. > + > +Another thing to be aware of is how the loading of the GRUB image by the > +firmware affects previously set software breakpoints. On x86 platforms, > +software breakpoints are implemented by GDB by writing a special process= or > +instruction at the location of the desired breakpoint. This special inst= ruction > +when executed will stop the program execution and hand control to the > +debugger, GDB. GDB will first save the instruction bytes that are > +overwritten at the breakpoint and will put them back when the breakpoint > +is hit. If GRUB is being run for the first time in QEMU, the firmware wi= ll > +be loading the GRUB image into memory where every byte is already set to= 0. > +This means that if a breakpoint is set before GRUB is loaded, GDB will s= ave > +the 0-byte(s) where the the special instruction will go. Then when the f= irmware > +loads the GRUB image and because it is unaware of the debugger, it will > +write the GRUB image to memory, overwriting anything that was there prev= iously, > +notably in this case the instruction that implements the software breakp= oint. I would probably split "notably in this case ..." off into its own sentence. > +This will be confusing for the person using GDB because GDB will show the > +breakpoint as set, but the brekapoint will never be hit. Furthermore, GDB > +then becomes confused, such that even deleting an recreating the breakpo= int > +will not create usable breakpoints. The @file{gdb_grub} script takes car= e of > +this by saving the breakpoints just before they are overwritten, and then > +restores them at the start of GRUB execution. So breakpoints for GRUB ca= n be > +set before GRUB is loaded, but be mindful of this effect if you are conf= used > +as to why breakpoints are not getting hit. > + > +Also note, that hardware breakpoints do not suffer this problem. They are > +implemented by having the breakpoint address in special debug registers = on > +the CPU. So they can always be set freely without regard to whether GRUB= has > +been loaded or not. The reason that hardware breakpoints aren't always u= sed > +is because there are a limited number of them, usually around 4 on vario= us > +CPUs, and specifically exactly 4 for x86 CPUs. The @file{gdb_grub} script > +goes out of its way to not use hardware breakpoints internally and when > +needed use them as short a time as possible, thus allowing the user to h= ave a I'd write this as: The gdb_grub script goes out of its way to avoid using hardware breakpoints internally, and when needed, uses them as briefly as possible, thus allowing the user... > +maximal number at their disposal. > + > +@node OVMF debug log > +@subsection OVMF debug log > + > +In order to get the GRUB2 load address from OVMF, first, a debug build > +of OVMF must be obtained (@uref{https://github.com/retrage/edk2-nightly/= raw/master/bin/DEBUGX64_OVMF.fd, > +here is one} which is not officially recommended). OVMF will output debug > +messages to a special serial device, which we must add to QEMU. The foll= owing > +QEMU command will run the debug OVMF and write the debug messages to a > +file named @file{debug.log}. It is assumed that @file{disk.img} is a disk > +image or block device that is setup to boot GRUB2 EFI. This "setup" should probably be "set up" as well. - Oskari > + > +@example > +qemu-system-x86_64 -bios /path/to/debug/OVMF.fd \ > + -drive file=3Ddisk.img,format=3Draw \ > + -device virtio-scsi-pci,id=3Dscsi0 \ > + -debugcon file:debug.log -global isa-debugcon.iobase=3D0x402 > +@end example > + > +If GRUB2 was started by the (U)EFI firmware, then in the @file{debug.log} > +file one of the last lines should be a log message like: > +@samp{Loading driver at 0x00006AEE000 EntryPoint=3D0x00006AEE756}. This > +means that the GRUB2 EFI application was loaded at @samp{0x00006AEE000} = and > +its .text section is at @samp{0x00006AEE756}. > + > +@node Using the gdbinfo command > +@subsection Using the gdbinfo command > + > +On EFI platforms the command @command{gdbinfo} will output a string that > +is to be run in a GDB session running with the @file{gdb_grub} GDB scrip= t. > + > + > @node Porting > @chapter Porting > =20 > --=20 > 2.34.1 >=20 --XzSEr49fHCefsqCN Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iHUEABYIAB0WIQQfOU+JeXjo4uxN6vCp8he9GGIfEQUCZIquAAAKCRCp8he9GGIf EbWLAQCP42svtcvbJRX+D3pBVMkZ3ofRfN+hw4Ze0q5k9SS5QgEA5EkF9oxAks5y TfiYKmbW9N/dpNHhT4dKAyNK+7/wfwc= =QWD8 -----END PGP SIGNATURE----- --XzSEr49fHCefsqCN--