From ee37c11b015fbff2013a1610b88bb1bf528e545a Mon Sep 17 00:00:00 2001 From: Francesco Cagnin Date: Tue, 18 Jun 2019 10:53:55 +0200 Subject: [PATCH] Initial release --- .gitignore | 7 + DWARFutils/README.md | 25 + DWARFutils/dump-dwarf-dies.py | 28 + DWARFutils/dwarfdump | Bin 0 -> 1006032 bytes DWARFutils/dwarfutils.py | 141 + DWARFutils/misc/debug.sh | 20 + DWARFutils/misc/test.sh | 12 + DWARFutils/parse-dwarf-types-to-c-source.py | 698 +++ DWARFutils/relocate-dwarf-variable.py | 68 + FDPutils/FDP/FDP.c | 1358 ++++++ FDPutils/FDP/include/FDP.h | 153 + FDPutils/FDP/include/FDP_enum.h | 126 + FDPutils/FDP/include/FDP_structs.h | 308 ++ FDPutils/PyFDP/PyFDP/FDP.py | 402 ++ FDPutils/PyFDP/PyFDP/__init__.py | 37 + FDPutils/PyFDP/setup.py | 130 + FDPutils/README.md | 44 + FDPutils/TestFDP/testFDP.c | 1809 ++++++++ FDPutils/TestFDP/testFDPClientServer.c | 138 + FDPutils/TestFDP/utils.h | 101 + FDPutils/VirtualBox-5.2.14_FDP_macOS.patch | 3994 +++++++++++++++++ FDPutils/VirtualBox-6.0.8_FDP_macOS.patch | 3864 ++++++++++++++++ KDKutils/1-create-DWARF.sh | 27 + KDKutils/2-fake-DWARF.sh | 47 + KDKutils/3-attach-DWARF.sh | 23 + KDKutils/README.md | 75 + .../10-14-3-18D109-from-10-14-2-18C54.vars | 32 + .../10-14-4-18E226-from-10-14-2-18C54.vars | 32 + KDKutils/kdkutils.py | 67 + KDKutils/set-macho-uuid.py | 49 + KDKutils/set-segments-vmaddr-and-vmsize.py | 75 + KDPutils/README.md | 15 + KDPutils/examples/kdpclient.py | 76 + KDPutils/kdputils/__init__.py | 0 KDPutils/kdputils/kdputils.py | 95 + KDPutils/kdputils/protocol.py | 299 ++ KDPutils/kdputils/replies.py | 140 + KDPutils/kdputils/requests.py | 55 + KDPutils/setup.py | 3 + LICENSE | 202 + LLDBagility/VMSN.py | 286 ++ LLDBagility/kdpserver.py | 221 + LLDBagility/lldbagility.py | 419 ++ LLDBagility/lldbagilityutils.py | 78 + LLDBagility/stubvm.py | 614 +++ README.md | 160 + misc/create-mojave-iso.sh | 13 + misc/create-mojave-vdi.sh | 37 + 48 files changed, 16603 insertions(+) create mode 100644 .gitignore create mode 100644 DWARFutils/README.md create mode 100755 DWARFutils/dump-dwarf-dies.py create mode 100755 DWARFutils/dwarfdump create mode 100644 DWARFutils/dwarfutils.py create mode 100755 DWARFutils/misc/debug.sh create mode 100755 DWARFutils/misc/test.sh create mode 100755 DWARFutils/parse-dwarf-types-to-c-source.py create mode 100755 DWARFutils/relocate-dwarf-variable.py create mode 100644 FDPutils/FDP/FDP.c create mode 100644 FDPutils/FDP/include/FDP.h create mode 100644 FDPutils/FDP/include/FDP_enum.h create mode 100644 FDPutils/FDP/include/FDP_structs.h create mode 100644 FDPutils/PyFDP/PyFDP/FDP.py create mode 100644 FDPutils/PyFDP/PyFDP/__init__.py create mode 100755 FDPutils/PyFDP/setup.py create mode 100644 FDPutils/README.md create mode 100644 FDPutils/TestFDP/testFDP.c create mode 100755 FDPutils/TestFDP/testFDPClientServer.c create mode 100644 FDPutils/TestFDP/utils.h create mode 100644 FDPutils/VirtualBox-5.2.14_FDP_macOS.patch create mode 100755 FDPutils/VirtualBox-6.0.8_FDP_macOS.patch create mode 100755 KDKutils/1-create-DWARF.sh create mode 100755 KDKutils/2-fake-DWARF.sh create mode 100755 KDKutils/3-attach-DWARF.sh create mode 100644 KDKutils/README.md create mode 100755 KDKutils/examples/10-14-3-18D109-from-10-14-2-18C54.vars create mode 100755 KDKutils/examples/10-14-4-18E226-from-10-14-2-18C54.vars create mode 100644 KDKutils/kdkutils.py create mode 100755 KDKutils/set-macho-uuid.py create mode 100755 KDKutils/set-segments-vmaddr-and-vmsize.py create mode 100644 KDPutils/README.md create mode 100755 KDPutils/examples/kdpclient.py create mode 100644 KDPutils/kdputils/__init__.py create mode 100644 KDPutils/kdputils/kdputils.py create mode 100644 KDPutils/kdputils/protocol.py create mode 100644 KDPutils/kdputils/replies.py create mode 100644 KDPutils/kdputils/requests.py create mode 100644 KDPutils/setup.py create mode 100644 LICENSE create mode 100644 LLDBagility/VMSN.py create mode 100755 LLDBagility/kdpserver.py create mode 100755 LLDBagility/lldbagility.py create mode 100755 LLDBagility/lldbagilityutils.py create mode 100644 LLDBagility/stubvm.py create mode 100644 README.md create mode 100755 misc/create-mojave-iso.sh create mode 100755 misc/create-mojave-vdi.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..77543be7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +**/*.dylib +**/__pycache__/ +**/*.pyc +FDPutils/build/ +!FDPutils/build/CMakeLists.txt +FDPutils/PyFDP/PyFDP.egg-info/ +KDPutils/kdputils.egg-info/ diff --git a/DWARFutils/README.md b/DWARFutils/README.md new file mode 100644 index 00000000..eb60e0d5 --- /dev/null +++ b/DWARFutils/README.md @@ -0,0 +1,25 @@ +# DWARFutils +This folder contains some utility scripts for working with the DWARF debugging data format. Currently, the scripts parse the output of the `dwarfdump` macOS utility (included here in the repository to preserve compatibility among macOS versions), but they can be easily modified to work with other similar utilities for dumping Mach-O DWARF information like `objdump`. + +## Files + +### `./dump-dwarf-dies.py` +``` +usage: dump-dwarf-dies.py [-h] [--children] [--filter FILTER] dwarffile symbol +``` +This convenient script dumps DIEs at a specific offset or with a specific name, optionally filtering the output only to DIEs (as printed by `dwarfdump`) which contain specific strings (e.g. `structure`, `declaration`, or any other). + +### `./parse-dwarf-types-to-c-source.py` +``` +usage: parse-dwarf-types-to-c-source.py [-h] dwarffile offset [offset ...] +``` +This script extracts (as compilable C sources) the definitions of the types (typedefs, structs, unions, enums) and the variables defined in a DWARF file at the specified offsets. The generated C files can be formatted more nicely with any C code beautifier. + +### `./relocate-dwarf-variable.py` +``` +usage: relocate-dwarf-variable.py [-h] dwarffile varname newaddr +``` +This script can be used to change the address of any variable in a DWARF file to a new address. This can be useful, for example, when a binary with DWARF information is debugged with LLDB, since to locate variables in memory the debugger uses the addresses specified in the DWARF sections. + +### `./misc/debug.sh` and `./misc/test.sh` +Scripts for testing `./parse-dwarf-types-to-c.py`. diff --git a/DWARFutils/dump-dwarf-dies.py b/DWARFutils/dump-dwarf-dies.py new file mode 100755 index 00000000..421c3c39 --- /dev/null +++ b/DWARFutils/dump-dwarf-dies.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +import argparse + +import dwarfutils + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("dwarffile", type=argparse.FileType()) + parser.add_argument("symbol") + parser.add_argument("--children", action="store_true") + parser.add_argument("--filter") + args = parser.parse_args() + + try: + offset = int(args.symbol, 0) + textdies = dwarfutils.extract_dies_by_offset( + args.dwarffile.name, offset, children=args.children + ) + except ValueError: + name = args.symbol + textdies = dwarfutils.extract_dies_by_name( + args.dwarffile.name, name, children=args.children + ) + + for textdie in textdies: + if not args.filter or args.filter in dwarfutils.extract_tag(textdie): + print(textdie) + print() diff --git a/DWARFutils/dwarfdump b/DWARFutils/dwarfdump new file mode 100755 index 0000000000000000000000000000000000000000..e622d41689f83531040926398df5a3a08adc0d79 GIT binary patch literal 1006032 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%0DVT(|5vk#ZSU&x>ZCxV>{qkrGLd#`fG@)}5*qDS0 z+fNlFAQqu5C7~D2l{I=)Bi*qGeGd^$LOY4nw&@-D^xKjm3H=d=6&kSaCK(t_Xaxcr zlTa9fs?@(G0kH_ZL{MhC<}FER>M04W!W)y&ULv(^I_#ZrrnGLoJ=iXIskyN?*d};x zFfG_BcvkR?U}|hDx102bjHE`*IMsY=-T87%EAyrno9{YskX4L2Li()3D6V>xVf6*$ zTcikm@Afw(W!DIA!z!%S_u!35*=WXs^m%|!%&Ib{3CM|Im!B%9?W)M>z`tcqYlJse zPDK$pB^x;<%AC3~ry?V#vf5M7R3LMjZsc_LDRVkEBBvJ_3-~`TVspJ`a6hH%;KA0S zuaO_lhvP_MHSM3=R%icYkKT8`DJ~r9R8eAGurBD^hYNs%UH+bmz;)YBF3XDiE5o*iOgz@%8Z}%}Nfxg`kzsz82Mg3?`h2{o zTQCCeRO@4l$tOHKYb$&WdFIg{RU?pwjMxeY!YB_5fi?UrFg`2s=${V&d$nanj05>v%SVDWFE<%oV{??Vw z!yJ7d7II6UftM?twd_r6MF`ufmtQo}Bt6oD^zwlw1Iha9$5HgzNS^}qujY(YGt#Hy zkW(Ff-5HJ*(Kx+2D!e7b&tv$)y6}eNzJQkv$eqeB8kO8xzDRPT_c!(LrylejPb|0q z7u5J$us2bLCQiXdrBDs%KI`hFxByRKMOV{~y)p1!i@?Ufn|l1n@Wjc_GbB%v|m~M)(AV^X#miT^Bxl=2hrXK89NXw-#djF!FZM*S^*1rnR>y+iW@PPMf5W-+|4oz$VZXDWR&#>yoqe zf9zo)9wx6#?kB}60vn?cIb4^#{`V<$W%vgPD9Hi=F0pChkCni^NI-M(%asik}~DfLJ+3{ zGC283oZRULvHlbs4Y1%iUFbHrf54gh>m&CkViymuXvVQ(otEKNIya_+xi*?Aq29sF z?>F3#WdB64xwuYY&#A9d5X*R-CxgZOwjJkzFmn;TEQnjcwL22+A6`*CW}QXMGr-7ZK+@yo}F}f3ILVb}*qo4@>>YcnOPU`3d$9$2+DETZfnH!Y}W}OXRrO z%#q4uABO!#xhb!Sh((v2&l{J*#+d&o%@J;3A#Mm~z3YcY%towwv=G^R#%w+dXH(di z*@)TZ*C&yh&za5V;cQwrW;W?8v>Qb>Uoe|5!r8nZXv9JjbFG^ulA14>&6nY9djF1W z9BuFC#R~snn{%ju!=oUB1;kY6L&yfL@znOo*ndvJF4EA0lk;%#vF}K8>onSAL1&u8 zqle+n^!LhMYExkGrdL4rTF@s>IgWtQ)}lqQoggxtY%R7T{CA5XBjFc1AmKS8F(h1Q zEnWurk0y326T9tCBrvT#5?J#`ItPQCzDPi5pnYifhYe&eZ(;pY9W72aWHDNA``?U{0TJtNI-P9;uPy2 zEz`bCg__47P#g?OS|N#Gw?B%&j&?vm44+1PXGqdh=nT<1=5>hOK*zkqOyW1F0l#*^ zgw!!R6$l03K4J;pr{<|I#eme%V-g`BgK!;SDq;-W2XKerY*13h!F>!D5B(+=PK8?o zcMARHXYOgI7LF@7n$JX5SmoQ{$xG-6RDt(hU4F!3~6I|SVB*Qp$3lm@m}gE4BU+Ozfa|;jiE}hzv1aw z=4b&LkovRFh$u-fbAe5Dhc+Pv?~*U$0lcInJe=yu57OcU z5*KVUd||t|V5{NS?G{WOzOYGLu<7uHXT=2*hcCP#uH&lV**!1D^+QJ$6({?L&&0u} z(4rXbC4^PN{ioi$&)|pA=FncaZE)td-vj1(onN!Jh6&?~rNOoGpREg4`Hp53v;on?ZdZpe?1< zRK>C{R3mKUSmKdF4o+<5xU)08%L11m86JQOpU1G*Ix_So`A!*|7EQvjX9Z}qi?Ji( z%XpTEc-G<(sJp?^Fqq_L~FC7jDgiyK=*<#--LIDPM^x|MXSuM}=LeB`CwKf6~?Bd;L$Q5s3q3 zXe7U~eB}95e^Tiz)Pk@#Gs3htGg|MDGKamHex|*d!BU|PbY-}l?LJj{Lt>?u5Ro43 z%?#G}k3>pfO_5k&2YeCjDOUZ}uh07aU$akBKYo3Gq)qMMtiB}5prub|xcHyw(po8*u*JE>RsM4V&Q)*k?eE-8f$5oPGdMQKDSRcf z8Ye#!V0yeoMs`$1i%rSja@KX$;&tTog%COXOK@c#66bwkqhK*G;vWnq zVMOOGvrcPC<}Uv^3B5+OD|?OG+I_p=vd0*nz1%u=A1Zn&c|+d{Y{jCnEx22V4t?%$ zJYny4Dg>CX46QT)%i62x)6RaH4|E=7_$rby2a_>pf?q`C{b9ZlWt|hjmv2kH{0QjM zzSN&hU+@&t7koLMdMyp9JooV5Ad5l1(am6lcs&QjGNyJOUFZQj~er z;SggXc_TyA?0R4b6N?I}!1g96s1Ji<*<4!RLo0QV%iHvO5SKxRhEg>dUU$E`vEOX4 zL#=?}KB1d?XR5r!Wm1_w{7IQ~U6Q(f($KWDGD zPMr%G*xld!HI7lN^UK)0hQj`ab2heIeGcP8C3X5LaKWAr-rw@I9(OH_C@>cW!N2bs z`d7G5gC32Y6(}$o7J$7@o&krwkmDriHW0$zShL;dgQD1j-g?_YxY8AeB9Oa6%J|h} z`ISBiPca3~Bygak6_UuVu0Oy|Cl_BN%pkzpGV_IoBhmU3P3qLVRZa@gmH?LL+*z=&7*9K0J8u@90M z)`k+}^m`=^QF!%_f!B?VidTT6fcnL;Wo^Uas6trd6bHp^ba8xlS5$HAI*H;KEOCe` z4o3{UvKkdHm*pE#MSJvq#0lMmQAKXzwEREZCwN#i5L}J$?9~a_(7_W~vMD^cb(mP;nwGaj!FZrm=9M|E^S_TyUzEs6TtT95 zKoOx=tqUk;07k4?9HO?D;kp|XSQl_-EE{8=u0Wygx@~MtD|jb(om0CJ1zBa!tOW0r zjk{dsl~c5Rk%l?i&+(~4J*Jn&XiQmjRs1{T&)2F~#(o}obCI7{6AODYfyn?+EyF$q!@w(ltBi9mGI z!bu;-S&Jtj7JO$3_5$XRQJHLo_;4RLq@=#zCMsKW=Vm4Q zZRIV&{=JV+%Y@_RvMR^af2q$CI?M2}lV>ExNpAv0RM0wfQF z1{Xnyel`dsPm6|+My?HLS$B7C%M|p3prL(~a2bPikPVUy90Xrz&V?|V>pPlio#uoW ztDz4GA+rn<%ve&W4%L`d^4#UHY4|)#(vUctGDU|GE+rwpWXo=WOb{MbQb(6JMoF1x zE5Ob_febxiFj_pX>M*ey1FRJ5(tUw4FufU=hd>wx)MMd#7x1c&tEbU$rRo(Bv;9V? zogqsta4Z77lkI@>(0xJ7OGsfpi2>Fn`-Igmu_7N`uUK)1K*t4MkVYu>z!kjv!1#7Q zzQyn;7OCSZ8j^Z9!u~u1vs$QS2cE-^??|Dw=$@mY(DjAzS&RRPUlY5b&@xVe-KJ#? zv0IL?#=*{z+muzi1~#y-tL2r3DmW{s^@Sn~JHXlH)@chcYYkI-xzLv0O2L#q8U{;S zzsf{P-eN2-tL6>HSij$h&RA{}C3hwvrclM8NJm*<^V^0o#q*J3a4zOoDv;Hmab3_Z zUxN1IF`k6~>;89^!QKCl>##k#(En|`GX(tp|I+=Lw##p?!|i&1#%)Ij!0-2hcL>it zA|3s7gCLE+g?kv`X4rq9|1-;CVvIUseKtY8vL3q|byy_V_@b7st zt@`nqXZbDx?NZ>0TV!&mpB)50d?E+Wz@?rpi%R{cg$Ue3`R(RCch2;Y%Cez z`#^bJ&^!h$C=0w(7j!kU#EeRCknz>U@CTm2dn{z|W=|t{g7~sJ$lW2t^ey=D)kR`D zkpai>9oBqPa9OX6(OP7WbzrPc$dI0wi72T-81fK_T#9dkm*Au>E}ZfmiMLKm7b_>b z^ht?C9n&2m+MOVbNtar}CTm@lJ$ODVE*Km>a~(b!1a}AA18^B|d$BYG+eo2FaM!}k zggf1EAK=ONJ@G6v!Xm$6IMZVsehan`eumX|%p-EYRsVjtTN4(2VMSLxf+}uh@PnYF zR$q~k5Qoa$FDlm~EB9>{vU&Ex?n_P#Xb1t2BKF4HL&?EO#^*g)UZe3UKKNH7Xe?5+ zTatr+fg(NF2wQiasAVYDEWcVk1b^@vqo!*0ewV;|x%oZcox{V@?Znl9liID*ck;*yeWTk7*l>B&kJxKlbXWDb%a+#bEhgEW#3{T-m;}WkJ zV%ujVX0S@E(^g_4S60hkT0~R}wo`ZGETB2k*B_sUjmh{b@kF{a*(Hd?lmG5Twe?TA>} z_HU$YIpv1Z!^F*9JuyS!1Ksx86}Yfm3k&vn@vnzVQdP41rbZgNxNYQ{3=QU7$ z7l-+hZR6kX(@MZfwiPt9&H2q-UD0Z+ML&uak-$zoFmNwEigG6Fahl`^!X&|$vd8k< z?&4ds76v21C?;Vo$3sb6nyus-d_73_~RI&jNx2h#;e}8ZMT&Oo*zR7O}8CQW~R~??7N=H>{wW3AQ0CL3i~zU zB#||;)G!}ytvg9%tt&TsTXz$q;Or_uQF85$nc%u!rV8;gRgVf>; z47Ol(1~(@H=5ril`)vmff!`Gr6vM7Yo>4VMM9!Ci(G4<%*!{+XIltC%8V&LaB4qF= zce`xGMKxT+*6+^|WKJ3)ZS4#a-e@vupS4=IN;0uXxlMnHvG9Bn&!u3>INgu8*iB?D z;(cIJDD?rupbWk_h%!JYCd%N-w_}t+YZ(|_28jr4j51*JwrhRcbli~rm9@wE%+F9D z5Ku{^z;AOCIYVygPt65O8l8f*=p}GEuZP8qZiTgY7JPvZ@e@^pEWq3F6#}EN?}5O^ z!rrLCkvF2%L#rd&p#wmhd##DU*Kfrr(-kr>8mm$SHU?HKF#C+=888h081#m+U1G%z#Kb>aAERsNdzHdZIPJH|7WpkRog$N)U-dY2ogRC-5RFHo5v?1lHRQX_|7X z){k_RqVUjla{uG%!=cdK!0z1x(%p}_v>ME#-hMC?dKz;tX833D(=q%w=FJ#q2jT`W zD1Wdv6dHi%*ND&ON<8_V-wr_>rXR~Otr>Xd#jfT9_1rLgYpEAjKhCNA{MNj0it|6w zoP;v4%?}Z)t<=nmwyCvBiW(pKuy9^Gn5oOaIm?)}G6F_RaV{#X8^WE8puo znpj}v*GXC*Ufr__MvoAy-u^Ecw_};_XcF#lvvx065_kb`_`F|EVdaUO!qC|TEf-eA zrN^m9Khu3R7CCGd^HsO>SC3CQUYHXXH@Uq(H{?6gyLe-q7K=3jI z2Sy?g`*7^|-{I-O95`J02w-J6;!VeNwB|L@jBnfZCcJjbO65dRBn1sJ0e5-_i zB-E7NF~et_^55*|(XA_?acK3l>Cgin+3c)}-8v!Hzf;r$YxMEH9NPbB|=Pjgr^baMRZ7K2H}58_<6!FN%$?o94sQvyM&7+{2t)oL>+&_%xn|RJQ;AO^hEuLS)?S!KoI|;|{nqscA72L&eSHfKf z*8}cmxI5tf3O5e!F}Nq-o`-uK4g>Yj61dfHU&4J0w+HSRTrvi5XTV(mcRAcOaNXhh z!2JboINTVx32;SlemKtg{S$5u+OsZ-)`pndh+cWhSigAcQMS6Z0!$dGRWHssrO zh8yy2Ct*Xr?Imo;w*X;7zVX+P0(w|H<7*}-;&AMhI|vSA>R@aF63JZ0m?T4P`S5&onh>t8c#Kk?@-tW5*>vX;&7g(oVm?S$Tr@55qHL_%On0626yka|sV892Wr}LeYUn5l5G>_;!ng?_>OG3Exk6xr9d&hQSVg z`w-!|5`KhmiG(K*#wl*bnM`=Pgr^anC}BV0(Gq@~@I4ZqNqDe?XAyQw_*ufZNQ7z5 zCfr%VFA{Di;S$1aB>W2DR0+R9I8nmy62^o&)0syYEJ^qy!Zi|JLilqDmlLj%@EXD& zOL#TmQVDM){HlaE6P_*MZwNmw;cbKqCA@<$%WR!kq};FX7IFhe-Gu!UH61Bdkcc z3*l}O?nW4!TZzLBgt72VIEQeWgdK#NOV~v?PQrZ&8#;adn-OE{14 z@(7srUXa&44CewP;9-PIBz!;NSrUGL@N@}}COlEXqX>_d@PmZ!k?=!=2TS;2!fpvq zA)F)OBEp>|JdJQW2|rG_jf9^doC2kA^F%sE5A&l7%8!tW5~Tp+?15-yVPBEZ232>gTq=TpiF z+y|hNzz_icAutHQdIEg_aEc-51W-eu8vrVE!A<}+b3k}G0R2(XlV6U)ZP1e&vmc>$ z^>nJym>2sy_L|XlQhoP|5&kZoAHh{d#QO~JHnN^Kn+V^WZLn>jZZrTyMBR za6{o9gkwGv@SFzsjNxb4KMnY;h_D3)Twy%d;kns>cj0*uE(F&MW4v?V+QMB0*A=cO zTz|Mb;YPw0!1>^wgnI$*4Y&{Bmco4sw-Ig!9QV#0hiihd;+b$4!f}u8wQ#v`ec^6{ zyBBUO++;Wn?(cA~z`YCi5nLtQXK>r#f^di7IA*oNodiZL{S)pjxCL+(aP{k{zJDF!Y=&Eb`OwC|<42_RU-#cK_%9Fq@8bbehP;S2{2JUl zaFipo;POlLxeD($oFWcoO9j?8&cwROg>dcRu7%5mD?<2NSj$-eR{^&UZZq62xPx#! zG29G&&N*;x;jV&v82!hebzPB8{q+xHelPB#@cKnT!Nce$FV)4^v_-wK={X1v?85*_ z4hF^g1*R2!N3seYz}RsQ)(;~`rSsAM2FejVJQ@|zUa2>D74zE9i)b9_`i?ZSPOE@6 zf)wwsmtvPKNbx}t)f}|C5iPr3ihgFy0iiKiEu!-Df5?0D@TiLH|385sQGpv35Ct>{ zD!76YC4xdj8oH&UxZpC%sH5OAZX+E*1&tI^f=42~O)qBdy#=8Jsq}(yv0!gOSTERH z5WXkaTM&Nm4zRZ%JX`pC3&L@Ny#=9Jc#}p#+m-V77KBm3-hyzE@?37?d{Smau6f+s z-I2(3`~F1#vyJ{uEpWoZPm(5ivxWa9xXi-;6nv?L9}_&z!m|aRV&R7c2Q7TRU^cac z@=U>pTlg=6ds+By!Cidtt`@#a{@d)D==XxZvT(KFPb_?o;B^-MgW#8aaIJ;^DF0_I zJVWrK7B>0M^uc%f;3^B7vaYl69ZGwJ55B+$o4dG)B7UnK(%c|5g6)`QTSAe4qS1CA*=sr(`#lc0X9nZr~oP1Rf^d}yiS0W>^BRLlHCB>M4TocDcQy9M#-+eZT zMu0XER|wE1;%Wg>vcDxjO7;x^X(c;TXyXt3(|W0YD^6=D`TN&V>3Gf8Q}W;0HI?G} z*H!5}wZ0OpHCEbBYc0WAZ|OI^<`Vo%t-Ibp9!bkh&DUP(wbdqMp#S0;al5m(xi_vi z?nvBV+$h{>xbtwo#ZAIh;O@ZPi+cpeDelIXaI0{7Bj+RBmpC4eZER20d*SxS9f~^^ zHv*?au;<_|#!bXc!KG=lqu(mx{2r$}rfJzPE#FC&`xqx#umPv~eqZk!iUYwh`-O#WR>{zG%J^Y3`2$$!L-@_)N)i~PUK%0F{H zC^2H&@t-r2P3p~PvM2e0%9+h1Ya^Bn!jmX%ZOIxXThz=`QvHg;le^mJmk=+x4dIof z7YUX@9N_Z>e`4Wt1+TO4S%P1-@acjjp%Z_M;Abry5&WoyLxN{oSky?~Y2gzDS6TQN z!Pi;%NWoWFxUb*~EZj@*SPSnfIBa22Bze4rI|?3P;dX)#wD7i%f%mcSH-bA__)EdL z7TzTI8@q$CL2%N-Zwr3U!mkK^)xygJFSYPXf}iujk6U=2{O`B$6N2xt@NB^`3qK@y zs)c6?{+)&I7Chd_!hyX7QRk!z`|DxF0yb$FmH;eUCt5Q)550<#!8m_O9kr{ zBH)pNxi}(ts9+Wuf(Hryz`_FrziHt^1h24gZ^18ExQE~;E!;)$Ll$-fdlLGtf;|bn ztzb_=-}Vu(C!uc<>`CZf3HBuPlweOn|3q-m=D$I(C!wzs>`Caa3-%=Rmj!zgdYxcT zLa!CP%}S}y3-%=RX9ar_`V)dZ3H?#Qo`n8@U{6AyDcF2uqUBc z2=*lODS|x-y-cttp`Cb73-%=Rvjrb-^FLLvC!voP>`CZB!JdRZRIn$Z z7Yp_z^rHoP68hnSJqi6_!JdTPORy)QcNgqQ=v@SR61pSUlhAh+>`CZt1$z?uwhw_l z34M!TPeT7nuqUCX1bY(tCxSf*eS=_6LSHA?lh9um>`CY^3-%=RI>DZVUMtv>(4QCV zN$AfC_9XNt1bY(tqk=sN{Q<$Agg#TSC!zmIuqUD4DcF`CY+3ic%Q zpkPlzA12t7&<6|t#7fY|2=*lO0fIdV{V>6vgnqDKPeMOHuqUDS6zoapT?Km*dV%07 zoBwXWMnZ2dKoa_{0(1tBq_#$$M+*97~=lp()Yllp>2d*csAFc=&z?I_8#*N4Q z4mTAS!`*|sANM%!Iowj*tGM@YN!&NMT=rHv;`YHEh#P=A9v8-q#a)2A0(Tv*3U?=N zChk$(v$$H^%eZy8PjFx1w&8X~UhjhIg*zNqj0@sU!HvURiYvqEKyU*0XWT=$Cvh*} zR^Z;meSrHM_bsj+Yl1y+J#qbTMYsU26n8dmJnnb6skj*K9^C!7$8pc$mf~K;y@yNU zzQO72cSqblxC3!|obq^F7&jJo0qzRib+{_row%8}M{&>MYH=^)*5N+EeTCbG+ZFk` z3$7RLa9lAih&u&04tFW840kgwf%`M=AsqMo8ehPzz`cp%aiGS}ao^(F!5g~M-V@gk zSA+}TN^xi7bXWR!xa)BhxZ82};Qoqx1UCow9BvVA748k(d$=^e{r~m%)Bht4z9hZm z<%+s(H#Ztt*$G|9CAg05Z^_Gv=iO{i+{=7sll_N`{9Z7zlM(L^<$-S_-e=Yb{Vp^z z{Ll?Z(ibsO`478mjj-45ei`0m-%9>%z5GYF$iLX;KT!E!*h>CS@Ah-@-@QftVVnOO z@9!Z0(|%U|s}L@;>2tQt|J@zs|6fF`rh{!iMW1+!{4cfnPuWracl}iU72md<=!CJz zo#0i?(gwO3hjC}eV?;A zc0fhmDu3LSm18sdX4a1k@Hb`74|1UIIW{wx_1=W*pv!xF`8Qn_;hn6(^XBg_x{YLg z*wZN2!My8?U#{S)Yi$`oy<3FCb(QcYc6n=&ce{~Vz-XW9^m9MvVa=u{i5X!Fv zdxua?73}SuoFv#ggmR)_?+{8zuy+V$q+st5$_T;UAry7k^dXc%g1tj1{RMwaN2B_O z2=)%4^cCzKLMaqnW8)auJA`tO{JldcJp_A)Q1%w=9YWbtaHUPVE3i3)k}p7qP}&R7 zA(T7;=L6&l&>@uV@QgWx@|}PnKHmt?A(a0J&>@s5wE#MVve$3SsMPRYD_r5N@2}sb zNuoh)X*`U%s~V?wdAKvyh((CT8r*8!S{!daHg3lKfXgR-XI!Du;RfL<@h{3IDS0#M@VlB5v4}=7z5Yh#Qt#oI16JlgAC0=QVMIm2YR!HVbgN=h5Q4 zu@Ap@D5VwrRyN1)b$JNCxdOzk^92aMdn|re^4)@F9)7Fa?ks+%jk#F^4A;`o;5|x~ zW&sh~TCwr!iki)}ZeAqOame5GD9*}?t$CC73e|4x!;>g|sKem{;I4S*+St~C`F)(y z*s38NCUvqFWQLod9<`k1##roQP%`3#FzMVgY;pgCXscrF{GY?%#?8XmhQMioQ(Xt- zLlsMS*`(TZc|6z<#Ubkz=QN@`(4GU3Jr+#%uc`i=J`~#oAB6 z!d07``7{w00Y-Je;8kDc%Wt9g+fBdRO3d6h%(6$1S)@>mXO6z%l_X5ThvK6PLWhpd z$CA5Df!?~6wBh(kJfn0{VIZ~(_0j#;k*~Kwb2^fj+s)?|Zc$I*J8e(uU3!3`pgND@GO;r1s*qBbh;+fl4!-e>M)2 zs>z}oSri7GdzQ3f%HG`njJJ=UoR=uc&l|V6#d{%rLtOEUVd~p^mm|g#9l5-1Dih%w z+!q_M=@l<}h12r-eAImUtreJe2I}447qfb*t}-a$dq%Uk=2tg$3%|NfxhFoSX0-55YchYj}OZ77X{3 zv+-Jo=g#7_LzMDB31eB;(%N??dNfF782zY;<0+p4sGCfalJVX9q8jddM*P)VZs+iI=Rk7tfN0H5hV$0^&tS zqXd@0kdEfl&zk}6?8}nIQbJ@fMBF#ZLn%KOIb9q0^&jlCi{Rs15M0{8wJ#&s; zfT|p8Htoym{quc8a3Qv#dNWvx8pe#5teDF>S}kjDF^lyyw(i}>@!MHqh*x&}=C7s6 zz&@hMJdWb~-iYbX0Tp+IE!{)OmVRU_7HI&M?fu+4wxT4t$=z#4=14 ziT-Lmvq&@@zn?>*)(x}EuC++yVHId=DV6vhO3{q45X?|?Kldx#eJIeF!odoac;Y~K z0S@v^5`2HLLYIc)LGqG?nlG5o-DkhEX71xfi+MWIoy8|Ozi8*Qy2eum!EB0mHsx94 zG+(;wO`0#=b^P+4WkIBP{2Zt1d6YQJtbKj&Ql{E8**EA$212SciCVnxgiXMgd5I+) z!x+PP-AdyZ7eGjK*AZ67ck+pISe<7GT8 zTb3X0*HFrXjJO!52y|>6(r+M}4N+PONw>Owx;nR2hDE-Gn|F279o1lN2Cic{P3&W!^O)W|7NP`RGRg4hWu;Nzr`hom2N!3LDH76#9Xs02^J;%$%z2q$hWhv4DFU(e^K>NTF`UdtF3ijOD=4IYtSmWRpFBkF>(IE%PA zb(UMlf#6JNZ|2we-utjj*hhn!F(f+3H&R3r<7Xj-zcHcOh|CzxdWXkK-IvIRLEuG3 zcRj5(jQk0AU2MEC0>*06iwC6nNy}1yGA!>etB&fcoo@SDWZ2Pq#FDY~P?u=_a$PM=6<7?njgb4 z-c5+-hGXp`vB`{<#i7Ok6U7q$BUq~T-~X#>s|&^ln+6ESZtCwo1u-OW{loljaW6Bm z(&J~k{WVsbiK^nJf}H5l;rLDY;bQESNv9NweV=*%DpXNdp!w<|)8yx9LJk*iD08t( zi=m43ar4Y)_S`I!Y8%~!gfKZ9lWHd@ z7Ihq=Uo-8PDaZ6-qTcBRC;npxoYwi9I%1=jYGMeseKYe-Yn#I@n#AEL)`Px>=e zRezcEUVTeqpYdR=yB}(-P~5BayJa6@PcIxFWgfM-r^cd{nGKt*hh-01&$6{%I97ST zH~i|A$D3#Fli7uRl2us7AhJQX317A6vi4e@7tju^+oxs#$kZ?0{>;{)ihmc_SJHR` z<^-O;?9i|%udKVf;&ZMw?L+fE{R+*ig%IqNJe#4xJ?RT+1s1De?w|#lq_XkY4#Czn zwsqT1I@{_uwrdPe-}WZAqm&zmUX;o$BEAtSo4@=Kj@u>I?VN`oQJxY>ic3DH+9itq zy~dZzm+YwhXf7jdF0U&Qi*z`u&RswvUcuxAVb(7j+&VZ&^Qtv?HIu+)!3_r^TWv_M z6H*sYZq#4yq@OBx4fk(JbczzGf2iEy+2x*`RqlZbl(s|??e_HYe#4h!6EE8A*y(7c z`SkZ3&1kHkDkzS}H$~W80QH+E3=nEFd;>ajUiQ)M>2rUAcDva+{7s2Ed3CsvM6{4u z_2GYI;}s~zEk-Ona`PWH;!pdvH) zT#${=soD5U_FGh_rJ`yzi%jOg-rOoX;yI+ z3l{y9-_(TA&5MUoWT4`Q#^`}UEtD7wvhTaPu!&p}lpm~E!W6xPvkj?%o)CmMGe_bK zHq!0T5s7`~9$*tOTcQ*+X{wdxeP!a70n#1)t4iB#mb=S@OZ%oZ<{USq$Y41qWhb}R z+B;3(5E3Rl_@dU(lAWfkX~TBLMC1n?dEEXhNLy#$Q0H;W*^$`MJp4HyFRjLNyb8V4 z7uc;7b?Sx{Du7?3=jGR%ScaJdp*n?%<~6|qjp|#=+!>T1xu_sKxTH^hS?EPOXKZr6 zd>M^ud>pof*YiGP9kxB#@CiA!Bl1I4HD!CqLU4>HO3gU@IF*G$A)nNU(r11sF^qZy z=cMHqjsMJCiYHstVViDe|E;!VP=IhOjn zhyXiBeaN1eS}oG|JR7Bfb5*d$>04So!MQ0|?er_<_0p?rPMQ#MZZ!5De~R))g96Wz zzL7$L&S|WTmA(;(y;2g}DtVYUyd)Is5ieN;OX^sQS@SLS$WWyNDkSzwsJlHIAQ5?=ae1YyH$Wt~z-HhndktDDzc?#jn>l>0WV6l^E=oQhELlw)Y!se{_p+q3R_>{tsGom(vlk`%xqAVFM z-W=UOP&}q^(mt%;`-bDg`Zw`ZDCV#gLpRtuZgF~ihrv9F zNlSKi&d4`2v3ZvKa_NdpYJEtxuqxYsa`$#Y7h|`;m z?P)X2pL|MZh!-{NV%H4y=CkGKUhhuZ)HJ&92COkJUw#cs3Z1GCRH;PKmrE$q+FWjQ z7jS)}g{J%=>zS=7ug24em#1LGyr7!A#KU?RHuExPehj6se16RCw3q`4u!OCmnN)#*h5YIfB&yZgQD zZfGq#29|pFXs}E##GX|wi2AgC6Gb1~=4-yIc2v?qwxnadlJ?kPNh=6Rmo!?jC@HMp zM3+73OppnlDV-w~z8ipyfySkkH6>2)iq$-u+-yQkWw7pD2*D(t+HKrkG5g@OPKy*x(Y{Ba>^%nWWW!KuTVBJn`ny{ z3Y5;M3(f+{aqbB|SQq@Kz`Ec=`q4~Cp40e6<&vIr^k8);evhgVij7_Fy?L$KXIBVV z&6aWF+rh+?9(jTIR3@~>Yo-!~$eJ9POlzXMfj&FWWz~)K!Ky~KDiO+%5o9!Z0Y|)> z&)iPcgYxyy-d}!1o5jaXQ<|zf2u(iDKjxEVAU>fk_=w143ZGcv(doly+CZo1Net%Y z#{~+Uid?E6TeN*;`FC8U-ojYo%w5Fh^-9q(E%qyuMpP~y;`cWpp1Q}3Z)yy$fWQlq zSeFD2&G=kJktMkwX_e~Srk5-1!a_!!vPxJMd^p!vijO=^)d7V$SuHrfsRe^OZb6R< z&Mgg^wxayg^w2b)P$62lv}XFR3i_@Ey@qQXXYSVk&fM!egq$a~Ixi0Il)E`McwZ=Y zYIT|%daL0NLq^pJ8}Fbj(;)gfBJ%c zI)2XAf?9f}dmaf=`-c+8FS8Mr+6V_A`Y(tn!U;rhc28Z!MqYX>sd|_ZEV+b#eovjt zPd;HiCH@&nU3+2aQ3t`<^E*cH{Z7knUCHRL8^zc_^pN;3UR}5pV3$^@cTOM ze4OrS?uJX^Ruc9*;$Oh;@wiiPnfWWNztifvG(NBoRB?yy#%vm^i;oyqu@znJximyo zjZ@W+ktxtn*N5{<@f!-I&3$-(LrJS{YCzqzRuk{lpoq9}^ESIfa*p}*iyO!&iK%Pd z&Q!^$o3^-rB92PFhhG)pRPIG#aLoB)mHlWDg^oJ0r9`q;0o&g-B|4O-&!qYEmuMI9 zYu)>mzo#1RExbUtmwppPT~w0R8}fTEyn~YNwk19EgDvT%)=Elt5}_(K<+1pwb=xo% zMmxku=LZI}1S&%XGhhW?&OHdtrxl_+uAx8?bJxR|%WvqK6YUhJWBm&z+}`0|d<1U; z;9;NYOB6j+1EL2LpsEhs5u&N}h9p0HZxMaVe6~iC-&2J&NeWaQs^3Y!iJ}YBCCP7P zXa^*@*_QMNucXU&SW+KfNe?R)xjv|0zkV^bi(md)>wf7)OuZw%&+I2;_}Af%4y2W(C9$b*z@BOCUSOe-<8-p_@OBzAtIooM zGy-*Q)&r%d^}u0G;Gib3e-qfZ3Djv^FIl%VXp}DZ=PT*eU{sXI!9v9p0AAJMIFA?41@P4Yt^9HEx5#l%p?IV__g*z z_gCV$NvzMVd{XRjQz=%v1@e21Yc~zSyIBQl-XZjxxrEZgP5*m_&}G3{IxjUVt>}0E zW<9kEZ1S9r=P$Yn-0uE~I#Re+flcKcKsiSDVV+$wHQw;HVeZXaJld=<-+tN{KAPp; zLWbUm+zwEz+*Wi<6b-oE*Ag$4G#d&`Dc9Ljrl(7}uth0!2@|tBd8!4};xmaAi9ckT zkI%c6DDi^WLR-1PeHas8=qR3H74tgGc(q_PgilVc(vqbvST28}Rn`S>7g!gp(oeif zxeZT52DOdE9~Y;E<6~JujkQatiam02h9^n}<;AKlCth*z4X5HX{8&+`k#$x!ID?m5 zW--0Jk?h<7r67GLtIb8H$3_@N$ZqpL;`vLpc^oAr+dmJyW!mGKXK0Vk;3vN$RgAar z|H+rqLhb??iRjIUrHUZMfkC-W`~ zPRm^Y^U{aVFBe_)1P9Wl1-3UPXRrt6>5vp~_rD0ZiU9PCO}$p?mqXrA2h^B0=xgG1 z;WK+Yi!D~PSMc|`?5QTj7IzH4CNJ{wVw*7cJSo=x&8Gh;E?I#D(VQ!rF zCCxzE>(!4b0o!=qEV zuM-`F6>)d8(9`3Et<&lD`YbxdlzTVj-j`yh2VWnUJ1zM7v|#0eJu_sw;vdBMdI)jy zh+{P!&pIH(($ASY)AZz~p7yLpuw}%~&{91Y!NYyhdS-agc;1I6$3b2}W3uj-Va5Uw z_Xdl3uVhvBKCa2Shf?4US#f_3Pb>@Gmb+l@OzoFGN}>i#F(r2)a6z9;z?mA6-y@(T zx9pANC#=J~#?kg|yT7Y_;BmwBMi>cHY|nG5_Crl+=3_hVJ@Gi1%pj8%)T*$a+0?oc z&tHa0S~iMG@v<52F=)gd%2Gi3pB$0p67w+TMfZkxgd0ZG%pg@jyQYw zv?Ou>eNxZFT}(8CFe-afi$qbilAo;Pt@1vK9%}18431_}H9T2#p%U#&qGiF?GQ%Zo z`I_6#ditsVn!CC9m!iJm(Rf*naN1Ki!CGEL);|VLx28*K(TO<%szmRt&f@GvH<9QR zlW67`dS+K+`^<93G-2|S`)5Bsp{2(V^^R*+wLthZj#RrJ9t*-e z1gd3-U(9JAob1r_DsTtp7zD@o z=^gUTW@NAb*kuCmQz~ef8?GzKGwTFrE=R>SxqE;CRkh+IOsuNTJZ5|2^r`~L7hBi2 z)~Oi4dfu74s{H#7&TS*N!pMm(e~_Nx!Vc3zxvw^UP(p3YATo|c&in3~2ejo*?KMk!}*tur?viNq5} z2E4ZAD})oYeI?F|&vqif#|GUVh7<>aYQ%Zsds?CdHoO+f_~wONMQKBnVD2|NVixVL z7n95!?Non}8ijQbDy13AG}1CF#)}^Ml$>tScQX}}&Wg=)k0S#ub=5r<5=*}e_-)<; z3r!F7x>U1e8A2k$)#*p4X>Uc2MUmX^La}-)XWt_^oAnwCnUY*Lvy$gr@%KbNL1PK{5N3%oHkC3Ssja_Wp!G<6M~MuT-(_ta&n@u>?_7o^ThjZ2-A zIy-f&7sP>kjp45dU8_A{m%A*K)Xq@qs8s*d5vjvchot(Z4odamFhC*6OuPOjJHg&$ zY7Pq!-jq*tWJZIrHF~J`{%;`F`#jYuCM{{<-4cmvAs5uxwO^oPc-8X#xPF zU!YF1UJgM85Tp#oo?4?Iv?QF6=Z3^2j=+s71Rf;iP4D!6CG_;`Z`!Gnq9^*G(%7Uy zqzjQ|jMD5ce45e!O)Bk+KZYH{8i>IbBN)8zZogXxTsdCHx!m{NIec-1brjb!%=bnU zIx4?y+gOmhRL1XI`Ar+u!TT(=pE^pV98R#GoxIN@?I)+*OlYA#V`z*f6|iJ_Kh(I2 zD}UWUzi*`9#!xXkTvO*yJiRo2Q(+hMU~C7PZX@PN%r??QNV}3K(m{iBa7`F{4O* zp3j_AC$@b#nQ5*Q!WZYvd^a%?9} zkpcIa^C9Ne-oC^Oln?2X<5W6;c7au%KAoeo{suZyGgN%P*{Rq~+Z+|&?>+JA*xH<$ zX~FVHqU>gK_T|=Q^b0J@+fO}WPJ}fjS`{kp=G-D9!KrIGueu@3m7H+Jrp+4aU#$^GE#qWwb-3b{TsW>=%)=4iX^>lr zKu&g?51F#@xMi|w4=oPM-DDBE_HnBlg5^f5IuA*g|5%p6lL4{|)dm$#Y z`5cATai}z1*CFzmM%M{IY$-UE+TL$gJtO8Wa}Rz=C3!4b&F+z1qoD5|Zg`9+iiOebrLXKexRi0bcCDo&vtYD`v+~|>7 z8Dwfa)!-4VFDWi`Zt35|N5vb<4hdF$?3mj=S!W~SFNlWjeiY0UQ|@**!0}bH%v|{F zefsIBuLW1aEbb91Kmt8@?4H`w1Em#*-i=Dj`0&yT8q%}Q17+=(a$|?izL_gUOt*hj z-krry7_yKA`@tcI#eO|MZDjUHXOJzzz@zWkJ<(qb5z12GBn`k$=rn)o2xWj%4yxzb2D4URJ1)t>?Yw zv$cAjMh@wEzNuK$>2>|`YU1B~^}M5BEA_nIdS=&iJf6QqJ(*d-Y+kgFv4e4g!L5~= zE_>e8^zHSwrcavB)@phwIizcvQY>nk)Ni8bj=%b9`cU6iYFceQvuk<-p1(v*rSJ-H zkCJ{`=iX{iYo(&^Eie`R0t*U^XYO+I*;+-*$st|QHeN+@Y(?kK^i_1>!L3yE5$l;< z(Ldn%OH@>AOy<{UA7hH)KGmSLPWs>HOilB=n!ay7TdV0G$st|SZYH-4Zdd*Ch8&|# zrjx#QP%AZk!Fp!bbT*#9XiYn+O`lQ?TC3@}iqN(~r!WKCSS; zbJjDP2Oh-p7v%w_;yU+VszGZ!(9YJhEAOF*2R6@D#apZCY$B#>dWgwwgWH#1-8aHE z6PK|gvGE9)qb0z-#6ONbey)knTfdpjI3`(+Hy7>t7god1_okvAN+CDSZn~!}t5{iR zchobRNQLAk#hKY)*A7Gp64&UbbXa_eM8exG<&BiJ$o-8nZc=t<>EL3g=jSg#Zw4{g zdnx+MpF!lP?D!QQK3lP}jUpl!*05dFZMZXcU_Vju5~!1XfJQW1^sHG2!Y}pMM|U6R zvpEUDa*I2G)KJhIn{iE=FWpc14aT-b;^S*9ArFO+fyTN>vFqHn0*Oz){YBnEPNlh> zCq1_==7?mStkMx=I~m1dUF9xmrLt{;0qboGlz7@>kN%I!y361LGGv0T`Q6xm5mlu1 zDF-VbmF`gV4n)^QBo69yKF68c0cL zC=50GV*svg=()%KVgJ}ptA)NS3Z2`M9(_Zi?--}D91_435C4f#WT0U=ZY_2O%QTy2 z-3eLsv@k1r_jTKI=9Zvwv;1e71iUf|lD|=vaxd z`SwLdhAcEx3aexK7^uM=N`byjv&9#Z->Ut8W_}XELWv&dgfP(`jz@bUao4%qo-+4# ze7f>a-QSV3q~Ipz`qUj~{e0U8pSs~D40dW8q@>sOo7a?sMS9c5R8#Vj&?yu;SGI!A zhx@feXN~pCLT3Sf9)W)WbovUN4NtbD&S?~b*c0iQ6I)sTSwn4ZI1%lc8+L-xo(0a_ z*8ooMX=J}cgiqES!O;529CgP{F#3P%X~cRyoAcdL4Z$%=hRMGXbMFqU-(C4F;T|V< zL?ZD^LX6;JBK=(YV_x(AxXkoNnfT@?tKYgts#~gSs&i`3R6(j!szYkGRDSmH)_S+V zE35;Uv1H4-59qgt*f$tk5?hrv1+i^eaE^`~&M`cr(fP2RYPC6B@;m|@vnS8GadbU!kmewJnL%<$~J z%G|S**b`U`-ksl;ocfT>axNR)id5YqORpD%N$vH5)x;6~Rw`}_Qw65q9f%-T5WRL-FRxuG?w z5MxB#p`WBn``oJM!Z%VTrdP3y3BWN6;-- zor3<)nBVo!95k?y%iOt7-(J$Se_zQjK_AiSiF!W9(y-MCrY13*4`J=AXEc=963k;rGea*(?f8cH(e$1-Gr~J^E(4nEw@Oa6g`IML! zTHq6PS7I;#?B+NXaV8nFKMv>2r$hXS+u!b>uFWJBq(K%@ZlU$hl2$t4x3hip$IpvLuUlrjf<@JDJ7scod>B zSlppZbX3n~@;7DDU72K!5{Nl(J(3oOKCUB9EuRo2%ejl$N!3@Wgld)@BeI%hXb7^6tW((wCkr47oojdsVb#_#8} zO<>{4beWP~pYA}sg$in&>%iC7I+KN2yQZRcrgerW3(Yc?u;(g^MSEpePBr+K^~K{EV%UfjIzM$3*fcvN0h2@@ zGR%VHbjwoEJ5Qfk9A+krdbb;?y+Z@{5=S!lpZWFpt77Vs9Ow7v!Cx8}I#ks-u~Yrb z2BY1h^T)^RncWsXM(b{avYMnlem|#4c9PKwK-|Y@jxf8A2Qs~)c81Yje~>JihJizr z#o1)xUl)qeg4QpqRs))f+R263jyleWc=QM(*qtDBMrSzfA)UHjS>B>Q14es-cpjr21t>=A&#%X5!^@`}%8knb z_SdP>3v4IF2OetJE*4zk&0o$fJvg(KJc7{ld5$>KX*`f0Iy4tcjcqTj(X@pLgt;e@ znn`N5``tUaIb5GDd(e)|cHVlV0$j_TY)bmLcDxPBBIqgj{TzbsB-j25iut(qY#pNh zv{R<2o#EP^%A#p_JYHEmKo-rp_73ZpRjV7Ciuylrt$%pD|9-Hs7kD+5o+@m*P)f@| z>Xlh(-g&QvNDK7Y+8{NN96jD?AdYzFYZG_e9FJit_1>?}mmnB)kylL33+Ivi(u{?Gi+`8L_g}SQaUb-xw;5PZ-0YU!5$NMmOb_X0Y7++9MpL z{eEyN?_o_XW1>yk3Qop-;9+SFc?4eQMxr{qx=!`->pxXUsBtmkT>qx@?2Mz79BFlu zXd*`!=N*ZBR_)xX7Xj0vfW&|;I{G`KgIJ;I!U~J=S#E{(Y~lrZUWw<=VeuVbS;>qW ztKIAV20!Skjh(!2CO$GfFgEQJ5s~ir@%+Y?s9RCYnNoF2y1yc=~;`nlbj^>lVi+y||vzj^E3JMjF)oA)m>vzqrlp>$AF zK|j^J$S~FJ8Ome-OatGeJU-ktgY1vy5spt9q&DqF9t?Qn(&}5NS?|syljaRw@1A*| zX=s+9MHelksDQp(HgvuF9RXfLPX$y%-@tG3k2H!sZ|?7G)2EQvp_G_4?h1{r)-$^` za+@l+QzP$Ab+!(m=j&{pxIyjC$lp0iLPa{;%6rK{mL6<(9j_eDAcvM@EFh*5+32apvU4f;FP)Mm|Ira|&sWd!1>Han*XW z;Ff2Qo9Jrg@o_9H&1jR_9eXks9I)O!ok_|lKm3QHQbj4OOPcFKPSvf{AvxVt#Q2m= zPTk}MmR*sGrorE&F4b~Zi`Ru7K4mARP=nY+BT^^Y;92fWf>T5I!VUxfj!H)(9Qo2u@!`sSEGGtw|G8`h2kR? z+}OAVwF~+;3LGIj8Vj%Apz97upXZ|DxGw_QXm&?BM#vfpje65C)G_Y!$on3#l#SYCacs^1`BBroNk zIMG%TbCf9Q(qB!;=YyeGaQQtR3zp<$^rf=Sjcxw7t$CDY8!ECrw?SH{D|xk=uEWO1 zGWT6_@w`V}Z4OeRE;O+Q+0T0Kv!DH3TN=L>RqL-gNcrotq5~O9$!3H5xUymJM8k)M zadclZz{v*dh(g)M=jAJ%rks;o)-APvxMEpI>C`;E84!G%Gl$VyNYFe2kha_I3v?7n zS+Cq}D(;tGrT;u_Onjs)7)1wRE_E>GGz&xVk$uGv*2ZcmhDEktfzojJYJHO;IBniD zT1v_J=Zxco&G2ra!NUuqhmZ+P5W=F?Nz_VbZ##vIrSYrDhIdtlcPmV%saQmex9yjC zVLRkAbmw{JKqNb#XY{qQ_r8XqLW9Ey28YoM4!kDvZC?1L^x(i<`d325UtK#oRQoA! z#`U>2lDknw(Rg7fVR}})q_CTef2q%&MxWLA(w2m8bFg}1SNiNQ^-vMKD@jtlFnGOx zKx0Ner|)Tk6b9-?7A#}nsv9Ypn%d@P_b4pDs=6PkOU3Qu zF>eDmj3_h{*A4yMn!5};!D{1@&j1VHahh^fKhr*~z5n8Sf23VF^Zv_Vr|Kw;s_FM% z+Go8#(k}DyzGra<|i+S}B=FVa_J5S}s|wUhZ!XfBT-i zgZ7QYCz=hQpV~<&qd>+I)A;j%P2)G?C*J>&y>>2bd>@GoeyNZqa{r`s&5c_pIv$2% zfe}GB4jA(CWkp6~DEBi=*EgeC@?tWelI)j6N3vPPb1s;rSqJ=j59T-ObH6?637DXCKr z^yag1$^D4mgm3121Z~n7nm^Y5!M=MN()D{iRQr8iI3rUr`mGHmZpaO>yuIxvdL~%Wh?Usx3?yj_qHn zmrY`f>0FvRq}h1d?Ecg}s#f~G75kK3HKb^=%)IKAyhc&7ycQc>)$hL{uxk1PR^fd%;3)jrch#ZepOA# z39pJc;Z!KTkC3jqmI;WfXZ5AU-$aiOOb-er#LIDgQ|*P2Gj|mW+K(usED314(Z6G`fE^k)y9qek=8Mraz0GY5R|> z&4m!23@L-J7PXP75a_W{#_8 z^$QohE9xDs!WcE?M0YKXU)ev^9{!0WE@io)2PMDI)v89Q2zy{THrj6TuBkyRp2ycu z+}G~)hpI5Xc>}DHIyY0E@*S*vqq~?qci}=Ex7~Cd%GjTH$Ni_tGTQaJeZz?x^O5H0 zC**Uq@+ma=PeOgkbC@wi#HsUBdwCy_I@LZMfOwXDgd<(wj65WH)pW*%-VxQ^!tt)^ znISX&whg_Q7F$^{qJkWet3waB4K?F0LbMrwwVGTLT5!5If;Wx7wT!>Z!?D$7{FPzn z6BBu#h~64}-R^J%V(d8#uA4_EI$vbw(AbpC2rEO)6c9UiGc7-4b0m>hANHO$;@2=} z*xo$lnD1srVS3$-jJb8uh@D@U=omlGX8b&l@$*c^%~Nn^b5wNHR#Sf6sLkH5Yw@{> z-dF2}ino?6E{&fcdTU(evSFui>{?#O8_Br3HF`jL|0w(ZYA~i=W$yz^94MgArseHq zUjow~=Ha}WF3C)px^STU*4?Szj;w_VKd7I)dL8X_n(wWitpC4Oy1 z{(*c=@`NY<;OCQn41qoQ$H;#SG~FcUoM850KqW$3LyoP{W3%hyw^ujbWNOnGs zLh;pKm+etn{AzU9_~fmD!Pgf|r1!A~tz}V!1)N20*}DoYDDAf>T>Dwuz@>{cE@hUF zneoh9-}McfXEBV3q5Gi4i=zicV%=k>?ocvqM5mJ2Xihtg%qR1#_jpPW55vB} z@R(3Po-NwI+mvm?nREQ5&J*93CeF+)E&kBCZCBBy-)5ckM-HtGz0@=gZZ(&nBgOS$ zXZRu{Oby|cuRv~Yt1S1Ah0J*KbwpT?uDltEjjM6DSJLe9aWxp8m`7xL@ zIx*y?NTS2mlM|g!jl_Cvjh()gN=3F(tCFpqPD=Et=ad|t1(C$%Tf2px^W^F`>Di$z zNg1E0b%&Z4G;^`5pLc3$Vn{91(T#h+g3M(sx!q%`)b8(>#!e`WUCG!tK`&K0m6H1G z>!5vof?^m%g9Bb0P?MC-OQYHz6|M4TM&8$akCwztJljfn!MBu0ZX>Z*A}nqD)iFLL z&f+=`%iP+`d^w@`w6A&nv-qoMetg8%!6RXhkn}9;5Qb^NI=AdS1>O|tw=7h`U3tVyQMcZXnVh_N;jlks#Pb#|nG7!=ZJa9$4k znzfI4c_@BTpF|`-FOE@yH!$>maX5Z^A1wvKhu+?2xv=0tu~Wl|g?)q)#to44u(iWE z%nPvZ%|#Y&&PRic9D{VNx%9nU{6^7iHPPYDyif<2y5ieC%FwZoTaHSoKY8RNH7N{O zZ=^O9bQ!C}>6tdp+na5ZpTpf@PH57voyr0>utM?njn&ggO6P3IW&7kc@qc4&d2x>s#ARWf^f#rLO1^PPFCM2iS^QQ?DGIu_7#pg5O%2H5^GE&Y{y2wpTZw{RC% zY&|vFu43yRWdo6`W}_wU9~peb{A=ztGprroWdzlk%lOxn!MY-o!8MBO-11xcMskLz z21?G{$!(NEWhqG{_Lch&8-0Y?+1IOLS@j7G9yfpDxt1hKKV^o^z$E64%CFqZNY9Rc zWlh=ZaW+x9k5pfU)T0XG`9Yo(OdP-DqCA>=03*t$?hx$4(!M%O8_P|8%PEM=*VDO4 zS)J$V8SJwnT1dM_y9eTXlf$Y^4qv$6VRkH8Lqr?&nJ;LAf*v9$w>GwW>WQrNiZ?zF zDwQ1|UddJ$K-{4d%^tv7{3sP{R1;3+<4}u*-*TGZ8+ZLEV`KwseX!wOgqV&JzIjQF zXi8sDGj=_vO-_)xeW;#7iNvp19*oHdhSqy}#MUsw+$ruJ<><3AiKds#X)`T2JC1cL@U-bZb@M^C;`^LvK5XJy#FMz~?@F2RtNHxY zEf{;Hl;_J$Ri;S3BZbNn|PLlHbzgJH+RbpvGfVC1YopF=?B5%|;9J z02^c_vEXb@8B0|A2-AYG;KFdc((A&N-srT}xs~UViCXs0^e9EM%wmYDc3(WxY(%g@ z(CFTRjX|uX_|H&`s{wuY+bK*s@uJ;kO0eDFer!Jdhg)Nd1GS&Fp#^H(KTkzav@c!u z093yF^4kzxSQup)K?54wxlT2dC=`Rp?%9RZFw(voy4OGUpeJB0k-aTgJybDu4wneN zAs%D8#!RR3IK~vJ@}M(satCt7LS#>dK6mMGda;bBEeqE4r+PKsy_H7yw`7Fp>|36W zkdr97zq=}}EM8J{Q|YSR#Y2grXLHGoy+cFESqhd~xoy)sR5hB{&%D93&?WcUJBP!~ zr@w`0*W~Nu(z0=T*rbP(RE^udMN-OB!`2YsHS7pLHBmru6GaDK0E*TkbECU$lUL#9 zSG}no1L_?2bepJy5*;pDQ0?VP)LOl6y57{QntQ>j=4IxyS-lK)2as#}F6D!YMOFUJ zZ?cD&V_Fbel0Tl;(MaHq3?*gE$rLQsEIW~UoO{04aH?C;Pu1gga)+&CBHr?vD$@9= z;kCkyr?UDCPsEslhJcg%WMQ<;lZQ+zt;xhN^;ZnPnQ9Jo*9hC2uCw*$IWX2~ZT9EG?M!>3%N9yxSoOzM@4$OP?o#G|+1#N>5@}x#VJh)IV zYs7}|>V7zW{t3TgTbA;x&`zdmTDHZg%uiR4(I7reE#w!cCvt=&wYyPp}6Y zXjqnu5^qLym@Ypwt}^TKv^l2#@ceTiwzdS#j=LP&T7h_w6Si#Veb6qFo3!IJ3XYd` zvt*TW{74pNnMBMYm%(qj+fU?d_M(os(8MVVrA{_HX}^w31+s4#*0}$gOmbS~ACn;d z?!LI4QeB$9IJ=>;UhpDMHIaSm+2-@lB@tw(pwz~^jLp*E`%uLV1q^~+VPrG2N(C26 zyjY{Yla@5+ou*-2w@oyeo`qy~7Lr+tA=%g)Xa0Wkkj6$$8po0iawXG-vEjU$mX=Bi zzF12L{?quJpBTmw6zP+huIHEqZDu`5FvSe>=InVrWmjmt|+&o=Po5hVK?DeI}H+twtPP1?x=xxk^6GRSY&Sb7{Vele+Ap&8ft%Qe*_Oimm2KN@y z2c3D_NsV!$arGF(h_-hxKNTKn=Pt!O8EaAXlWQR@3}qDLi`)pQN~Vu5D2ZRuEzq#E zO>_+PNAa7kzcJjm!99>@$htoeC68RYjx)JFac(Bp!mL~yHjpbhDrb^?9ZDF3{~9hw zA0)wMEM(Xa${ed*j>5n_g%BNzeTX}zLdB`9g3jer3%ZtFmW5PFEX5k4UX@8ukTchu ze`{DQ%%*@6_3grsH|5_7t%>SQ`R9^=%rtM?7FI5!JapkshQgvld#9U43${ZD#B%@&^^iW$)#q@jMnadj7yfgfc`|NPdm*CSG zR5zXps&qQ4jr{w*xR)vDsYCSQ2uKF!mvDBdF0T(UD6XuU!-=|qSfl8XJ()|%$f>*> zUo!hsAFtjD-H*(Yrm!Ok)5Nyq8emI^3&@4!NoKI%U-LpkPy1eEyhfqo_ zEs#U7lm6~TCB54m&k{H2Cm_7t4TMXb~U@!!kr?SR*c*Eu6! zWf#pHkACb*mJVY0swaq(d{GfSHa4BBfm={2?n*JG7nR3H4(W6Np`#s28na`p+nv89sUpBY;m za8oyN&p2pj#+J>zH)SfRL276r88IfBj=F4}`XdU7Qp!pm2S$qLR3fvh7dV+hy2Yi;B{sW*>^PAw?3jq;S`Zw8lN zDJ$JH<3tzFRUl2eAJL#FT)J!V$(yTdfnRx zpz9tdp^^hJsL_rgE4IV>ClP5GmcDN@DH2H(y~-M0WKlMTHMgGPYm17t8W?)KPYjeI zZ$elI*Ps|O1gxHR0WpmI*mNdNrFKY?tLV#VFLmP#W&9`(1Vz)6W)j;B#fjhNkXkfn zP-ZFSKDi5Zy^&xc`18i76MhR{kFa<^?zcIxD~z0~?)W7S;h+0aQ(HfCxxcNazip>t zr|JvFm*h77xo2FE-3<5tfK<@My(6n%I#tv0w&ieZJ3S<(y1QrcYMQTiR{ciF-%_>w zB{snXU(!{P#` zYPA6v^}jQ~F!}-*Vc2Hc8T@j0JF(c27qlh8h~s%!T1ezWHZD7WOJDT$Ofa% zImzew=b`u>>az@rQ{OZw9_L5#Qv6h_e5Vo~Lc}yRd)S|Bq)paC$+~+rJIYH|;8dcA zPA9v{pUknza{Z-!|As9MHl{zMlMV7GTT2w^_|Zv}W}mETbeDL^hB=jM88bX2bN$Kg zx5*YOSsKZNNd{G8|7c3JYF{5kerr=bs#NJlJJS|M8&pndN_MwD*+82t?oU?eB||bl zsVUhwf3j`7lS|dE@K-H~AM~$d&ct?Dx@rgclP$B!O8uohQ!}; zJ8ZHem8`edY&UqxXtpIy$>#f$on@2tP_mw0vJx*D&GyHpWH8qE~EMHzkP9)IlowFP6KcQf|C|MSaovag<^nU z4Nhu9jo5(o(j;5;m(eG0f=@Y&)4uP%0I#53+e-(7_T$(C-J%8&i6^Fn#U{B~eDZ+nSDcACqK*yY2#Ut^Yi{?>yvL-UVPo7|sZ=ABUk+sU%;W zbVdL}m{uoQe{C4eckC_q-R|ba9o6rwaK%jpInnF1S18>Z=TD73(xbx^^_X?w&@|US zu?Ce;Q8@8}QO+iUvvk>Qkvr%B>F4XPXE}>}%Vslv+;JkKKo2OPo;;akx`{S!w${j1 zzovXuHK&4gZIN>{jnN4&hAF^Z)K4!9ReawV z9UF+Z=kNrYhdGhNoEidIBBOxheQ~CDE#sx^jjhKbQgwPKonrx0X1C#Vg5A{p!0p|Y zVj7o5ic@9llS`Eca*i`^M2E)Z&b*q6t@};70%VbQo75}RHyZXvN6FUic`rlHk;{T_ z93lj))=&PU1@Z&1ujTNYHV11U4{$ zHlD)K*bVMg=ga1y&Y+y-8!-p0M@jZZy2{f_X0Vw(O=Pu|<{Zoi?%=%|8-rD!o7z_D z?ZAOZPI>$)9=kT~uMPx8Weyx$B$bLAVgjQrlJ7IyLW2)SK?8LYQT*K1NQ%7aq=2^- zfNX67MkoNgh6?yv0e0E2#XUp;c{bo9f509JNKqCk-c8x%0s2K+G6m*O`H7-9py zRzSLfVFmQI0sASdbU=Rv>}CUc`LWtt0be1zQ^6;dRl4TeR}!$+22?2kg^?l2CklAU z2CPtktb3?ERx99P8&KxQ>S+Ze2(Vgnb>*^=sf&GIgHmVvz79!^_I>q89q;?^Havtrg$YF*`U_lL^fn zpSXk%XWp0&9KJ~mZ!`mEz`1QKz6_oV>0BAzG&?GnfWiWe<<9{y9h&f(%YMU>Kq9ZwV|79JvpKrSz;Q; z$f^+*$}{qWEn4&C%O8SBRSjz^p&YlHK1B79TSH4zZ)aYWaIe_PP^pYXhsw9jH0K>7 zS;+?{;aC9G05!Y5YXzOI;HHh|Ed1tx-+b_^0lzsJ{MbW)>ca2vG=5c@HM#j~>?do+ zZ*cE?%itbyf;=EpI)|Z}oH!uFk=r%V{^7)R-2}&0IrBz+U9t7pG7iVS%BjjxTL%v} zrz$xC%5HESrz)4ZW$zH^RGtFz5qk$U>trQSm)q-{ZA+{PrIO;sPQ}qg2dNp%+Dn*9 z*a#LLnd6l+GRG^I)zC!tc;(a?-elxHNfN0L?>D3mQUP|L4ID@=7)*LL??|}7Cl4sz9<>_+>hK@mI2IRu>$e~kbM4XY|r=DeR zc}kx{Qje2sC_Zzt7?Kh3;M9G_V`{l15AC&2b5A)NrpiQdDkmv!T@C-u49$5i7^n(O z3>o>hR#%^e4r;V~o6|1D3D|zGM2`w5&ZddjZd{4nK@%OICdxYaig@-;=HTl)DRN(< z7$I6XRQm;O@u?2Fdif1&p5Ku_mERtLg{p;3&w)mp``(${zfkT2Rs@TeT;rrB2VPKQ z^EvUiUbDZvoA!A(?x5Whb8Bc4f4jR2Nx;gstAw{qgKz2~BsgHefWvlD=upBi*5$EU zuz0CcQAI|&Ut+oK3{xx@K7Izf4Ywuh`b<;FS5Pv=)&z=|Og!*8*g2=9-v_z1aNoBc z_my=|e$H5K_W%7%S&5nuq&X5i;7sv{;TWfiS++5W9Bijq!@p&Rr7ktorTyA3b&l_= zAXSS;)dH>;gVuv5L}K(AKI5yMPaY=w^Xo9GlwO(puYC zYs;st8mq`d1rm8kP^$QhkKhx|x(bL20g?RP=g#czY#tE&^z(Xs|M=zg%FfK4JNKS@ z?m6e4d+xdC5(Xmu=JeLo8a+OXQom2xqB{&2F9WpPovQA__`%%ExRLsA{hUhwizkny zz2pA3PM3Ebl_BYpU;gF$j>?vN9hD*9cuqxE;G{Bjjoy#5cZ$4@;h z{*;WLUx_p|!nJy%r~^oO+3$F%L}qAo6$PFsI`A|3nYq)}JZHSNWIgWDTD1Yy$Mzhh#RZZfx!QfQb9NEl@dAk~>nr!!_v~CC5_v88VWN7) zeFj@2c1M4gxEWAnzOM?sBr+(rXMA*e+cH*w1=t_`K|WuozSiPD05gfxDPc-Y+fNoa zpNPbeZ)$ZT*W9xTn?H>)=9jJHM(b-=!LH}Jr4@V zRyeeXmmz74G{M*%C886qmridC)Kae)U>3=>G4P6dw8FIvxQjwoSe&z!BgmWf%P>x? z@)Z;%3EJICBSO8hryVY9d@}1tWf1?NZT9vTAb;WjR2X~xv6hx-p5N@r?p2wKer~`2 zn!HppR1M5a?WL?%KEduxK>$%6*-YVcJ`2b;1{e@JURr^NIJ0ny*lmxUmrHz%WUqm5 zC67i?$b2}habSJH>NXoB90QN&Aa0E#s8L`)Y@9Qd@$5HC?{&et^>qiEt@PMz!DW?$ z%Z&-K6V!i3PajUZ){Af+d$U~CVAY8m4<+w*a9kWE5r1B=?(rUs6|ZaZ%;u&<_l zQdOU^(#`|YOZ)!)i#v(*a{c?4)84dy|JV3GV8?L$`)hgAo`3(j{T%=PA&2DOKMpg7 zvPqMdLX8)TAxd_%h|Zbi?C+63 zt@%es;SL*XkkGP8S@F<*6wAnAjVSP-ow5<`zhR7!LwsQuLX)$Z2WNCR^DJXVI)P@Qzd1w1>&e^{8RakA%yMp>zwGr2!X4s!!ssGrlfpc2^F-Vg^%tJG=WA%oSU( ze?I3K8Jm(1s`wPG_Mg3~_MdTtVr3n>Vs_cb0)Eg$tSOw!ps-T<#{N;SG-XDv^V2&( z7PS6U&pSc2aLyk~9l8ubm+qbun8V8-a*$FQ;c5d4W@o283Hvd+T?q6|X9G%8@^BpfAf4jt^e(S# z_*&B|`*YZwf^RT}QSh_N{(ArZmayxZDdPaM$#oHb(3<7UCR@kvk;&x+U5ayZ4m`a? z5WOfAiWpd=RnG;b>cZR&qpuTm}Ct>0w1d@M2sQ$?LOpwh`eF0l1gU-YP za;O)m1-`yY&}s=F@fQqv=J1rC zy`K99<>OTg;LnNG`(@xG`AO#B%y|1-BD7Vqn>er`^u z3ATvmIRt&&4)*4lNlRNr{s_6$weVwLYw;muhl7TZ*n6_pjN$6-RK-K#h z>rvNG&%bDoDj_-~!Tw2E&O$$A__dQQxyq$QTzVaEEKpTvzw&Y?Jp07;AHNV)E%HS= z3*jzywgTftG*uS|@qO|xdt_7i16}bKd6#@&)=seHb@-1@c*{!u+slJbfJ#458QynN zju^DsD@)Q?XX)@=>YLWE9OxUVJ0%~p{HFCEcS8HQ zUA&i}&iAp>e?+lKc2TxJ1ZQf-yr5!foBg2M?B?D3)qc=E)%9)@B8b_l%0;;$tzt{4 z^W?fPku6wDR?Ry!{Sh0^4za|u36L;!1;MgiVZydFwB!ZT@#==KM^RW%S<%5CA#NrY z2Ft!g`l{fhtLP5->*B_%+t)ehTNBcTZIN-W`YnZMW(OcU=-V)3^r}L^_<{#Rt&Gv-hT+RG{T+7ajSUpUPzejmgp3Ghs%7r{X=PP8XQ<$*Bq+*EWD`pS z#H`J)D(uQzNlU!&rw?3!_V`Ny+yn+ZnEaCcI#(7CPvFuH)dXU^$(+daI zPpOd@U=_ZZ#rWt;s__NZc>-}fvm^RZ;wVy_#q!CJD{+oYpZ(9bB>+I9Y^E7JvLN~+ zX2=^u=FP;*x~@=k`;+ZwQEri2q+URA^QVVye8V~yN2dEb01c;-R5phG)kGq3wBo;AZ1HoX#|3|coT#^ zu%3`tJ(M#Nfz$}SB_Ts4j1+_D6MHHv4-d)oZga@}Q}w(g*=WawRVi@WhcY^-a3@ZfoQ0erQqO3XEhM(HsW1a>$j0SXD5F48MXg zeieD0N+XwmUT3VdZ`cEp8|rea*K6@A1uuRw=7a*VsPv$x-iqANx?#~PvKlpfD1k#q z04n<+(C1OE>=9BB&R-Zdn6GtX?2nvMmn$`PeP6bo!O>=6$S7g9wJm5ghw1%?aB>n$ zIsE3G3unoD^u^azI2Q5JxQ3Y|v@ix@Z(?Rw)WryhP%%#u|1+-$@n;>vo3t(6P6B1ep>F3yc@KnbX$ZgK5sgls2&#)*F zJx)6A*>^x2TfSu<{w{k6Jco=zy=Bl>eQJ$_69P*)LHLzqKJrgut?jaVzMHE~G}uCc z@LPR_`J-auw#4e;4b&#@2U98+mYVE>x~ z^;&7Tc8WG9P^*=N>!)Z_>&Hydwb{gijn?d%rb zLlxeNz7+poeyLwXi%b#el+nWROpu}6F=(3U#IgpqI;gYbd@s&8LT`9KPdD?3B1rJw zf%WRrdj5(2I14k{B6O8Hp>v^`CD2?JwXFm!W%y90(5v1`QJc((pEo_!b}?TvJ(~~4 zTc$8gQ%|YI;~HonC)uq=cAAT8nKBx`Vp|j^YaW<}z-0h3oLX#7C?QZ2Pr#K|GVmqj zyIO0;dna1og0ah33!(MNt1xbS#qV275DhIRyMmEcn0wC1)=$Y*ZJ`y2x|uf>qzo!#G2hCqNjQILaX|Fm}f2lyQvz4$Ch0d1+w68xG4l+rlA( zMkCF&mN=KdxN75LJG{G3D7Fpn5>Np;_VDiZLGbQIm3nZzyOIy!-B}s!b+s7X;gOxZ z5jpuUwo(c2s>F9ByUVR_Nj{7^-sO?(*mVE z<}YRosB;>^My1zcO;vl5IL%-Hy+HTCS}H|u*TtJajil7e)p@xzb6c0WU6i>Me?2$v z!pv<==5~JOwkmTwD|0(Fb9;T}mUGTh-0aSIUxsglY-p64Pi!9pBpjEEhs4U%?K^RMM|hQX9v6 zC%;Zv;(IUUK1)WqHpvG#1+!u&Bi^yE<>r_QGUI`NQU|cnwp#AA>e7_wI8PejZ>8WGZR8O6mYVrQpKB zPmBF`GCFn3*!pMrjLnpN=piRO&BylDR4W*q%RAZ9TKxt1X{VPCQ&pB9tv5 zTGErc$=qYAt>-?Us`FZb$EhEBx{)1{_NW7ZnB#z`n=7O^dydNL+~3N*@~d@Ug0zp= z_GJ%*!Q1U4`z&wOJ1-&c3zF0UcH)%ph7Ky=R{G{Jt|^it-o|1D(@C2GIfF%NqJc@Cz#iJ=?JTgw$AeA9583w*2LH^3V(W;-%hnTFfrRb(wb%Q*_8V509 z-y~UH3tQK1azYy}dQ!H>tdkR)-pH;C^{O83G(q0ad%%s%4-2N3|hQ z1)LU%#`=>({BI8MR{h~P@-|3P2UX8>s+JLSFWKbU%r$i!In_NW)jjzD+Z^M9`2lWp z4vtV;ta{>i=~tfPo}5>+c}2w%o8w#KddVSV2R?#}v#y(DcjbJ64~Im00U*jx5cqng zfoKEYoq-@FWURZ%Ce*fotB2Y~uvg9=L3JHaTfg+H zHW(`8)qj=Rb_lf{C-7yc?XhEk@2^O>4&XD>uiC&jl~@0DYHP9AKboetxu<7Qn}O}p z-ut@_Vcnd5)dtozy!t`58}C)d!HzWA@h+Ob$M9y zQLL|QBo+n9kw>lOhvr14$?-ZhfLKepRIO;@Hz&f(`&q22k#Y&~o3Y=prK@2ZNBF#^ zzqnuLG_bOrzTS1ZHvBaLsMqBYrhOe&QqB&*C^y_!6ul_%COSjLUL@(qd+go5BF?Xh z)#ot=MZZY)sNua9{~MpU^Mw|r?z+Q_7MJry+YBngC3*c6x1d@aH5vlkep+iDBRG-d zTSPweF9vaE_&Am4)O|7cYR_?IDB0SR!Z!W34TXrN*^o|7Q->t~o#aE|GQSe)6({ZQ z;1u&zyvBdgB(-JEk?NBLvJ2B7q$Q<*&^Mi&1zF)CLe?=%fWWl37>F423xvYrPe3#f zo=jReepIG09A*(6kQjt$@bvd`rpLGu;Lv_QLO<~iqF)Fy9sf(hg$B&Ygh91DG+D7g z&G_WI>L1FCQ8pgn zI~jXxR23st6)Z(O&kJFI7OHsOS#QQ$OTu2~HtR1o>n$(nWsMcSlDSmStXmaxr2tTrB*g@Pg{}jLP z1r{@4TU-uLh!T*lL6*|1-*!^YaZ`@as-GpL6_U3ZzN2MwsNtf*E!r{t52N!_qkVF(K6igZV!t6{jb|_Pf9bIlYW(^ z)6}b<@aj;w=)jthL$6Z>!XwkVhAzxAFj zNZ#fB)88K>-#<-S2P55s=~r!9H&<%?ujzfXPC_yN@>H59yY?Lc@o*vFoj^tA3ZVpM zM)p{o-Q3m{(>m07aUNrb?yh^;ymu#`*05P5=vyz2pwB9L_Z=7M%~A>S`NilLi3^z- zW{)4b?^&(7FUe|K&nKM}YuEGa%5V5a%43Pe;UBmGKcUz<3j92}Gl?npc`g2aCch(3 zzS81f;FF3x5i1GuWXJz6PyXMQC)Fo=<;gw6p}iX<=>YQNgpxM$WTE=Rktg?Z-%6g` za|FeXs_Bs9&P=~*!*M-%^1Q!{3@>jC`KPXRP zE0pcjaWQi(E$vv-mKM_%Q<+ZCC!ZObG3_nnO*TnqjA~;}a;I(|+b>E17}Z|j>Xc6u zar3h7ndo=jnJ=s8cX_ffRLsA*SgG!OwO%T*%8ywE`g=LlVUM?jtelCsnALFkb*vfP z6-4DHc!2!k#MVHnz$i}sMdVM#MmbHsWuzT2rQo`Zd$+UYv6x52wJ9Xe0*b#r?dkdF z7jytcTvb{A8v-h(D*E|>pg73`#Y1HDK=FGXD{>X5>Q?^SKchtD9u}84Q>5IMNx{z# zvA2RvSmNZzMABG+M|O+dLJ_;-^}Nk>PDhrm{Ded(st_WAq6)aQrKp#?cqr;ULs3)? zIiaW?;Gq>o4c*UV-Qh!*qR!~_trT@7GXN;+=R-1>8m5xG{iURe>aJ$qxfFFfw?a|3 zay>+fx3#HAR(XQaYljM<}8V zMJ*%|iW=uolnNTqMn?T1AByVZHue`-`H@jW9g2E;SsRMl_H}EDdb=eRkH?9A*p7_q zdYtO7Rw$}3*F&VJ)1*?XeA-eEMb*vipf9ge;FQnu zWgr~W20{--b*AuCU&hJk>B~EKY(r6(=YeD<(yB41bp*wGMG8+lu}Gw3RFg{b;>iO= zB~_)MXd$Bqid{TrC~DA@))e*Hdv+JPtc|#{;)(kMGf>Uw5A5W-|cBlRP(+OqGH1e?)9>i+zwGad5l8V z+Ve8Vx?d%Ek(D8;i$RD>RIiXti0UP-heT9F{g)b8uz1QtR99AaKvYL5aLP~iK)C07 z0<;%G2}$k_e2Pd$=g!c6-8&sVvlgz9P6O&->8bp|8{N$JCz5j z`z%l0PfAAW{ukGZT&49fs(mRQ&spQ#y_uAbXy_P|t@4+0Ale?(Eot-bYKvC&{Y6f7<2==!+NL^>GW!-U=~7(7Hro>=BiAeCYL!oy z3PtWp+A$qf_*zRl-k+P7FGdE4iy@MoS1Kb4FW{)t?z@{^=O$M1Es{ntA?XyHVs z!kau5Rvf6pJ-ke{@OH^ag}2Jpy01o*pT_t?F4C#oN{YHQ?vsHWvq`b!aXe`_%vr0J zsA{;0H7Wv-tZYa~%7Mjc`)h@Z4UAoZm9}Al7%kpEkDhOG(6h?PcmZd1DhAl&GX16C zRW7R0FOx^&-%oTR2foOcOfflGVpe}#xxWZ%rxA-YK8`}n38hlu_1u1;U9BqqLc1dW ztofaDzR-SHzcL3RsbhM9Rn*W7O}lW!3+S&Pt4W_M*-MJ-E> zdo6Yau)-+f(ui&IxY-Pm3I z+#?Sk8-OX~Zb`}-Y?L5W*@pqL5`-=GTC$1X#A=m&vB1DK{h@I`wku9114 zi#bjr$308nuGUFTvGw0P{H*)dY4NuO&E^9NRQFTgCapRb1q#122haz`DdPh%J=yWG zKGNGO9}mQDA)I#w*)C6C?XuVv`L9=Zz*=YTg|%)0uN^B&2VgC~Iipy{(}hPWO3XUp z-Tovz&5tUfY2q$ZD;Sd-!YY>F`&7NovbqD*OAqQ~b5tp3JQ*>|V=N zs0|NZ@A|S(g~<*8h8!gy>C=!oivwW0hHzE}~MBS-9CUMlj=k?UqB9l#4keiIXu{Y4613>qvAj$^dx zB+#lcfd!WeQwXqB_T%!cRbC)XmC&(5y%)@Ct7xfq zeR;TQYQCqbZ>c12QytAt<*%Xel%jQ%R0RDNa^++uoboHzupt=5psjDC`gopG*}~H@ zc)h7jWf{DdP%e0d{Az{Xsi{_SbNwS%!kwiBi0E4~Hvgu|5g?4lIlBZ4YHW5#YI}g9 zE#;z>_Q~p$^2utf?&ckrl_!>r%N%YfHD2Yw`5iiBt6i+1{`P_S;X0Zjg!vB#vB-WA z9e2*4@2v9iU(uf@y8U^hq`A^4W#?8XE@77*q+8CJEbcgLcT-8({V8TnmA{^s^rtd& z4wH!E@56efY~ZsC|_ty6W3^B6|)hk%sRo5a^Y%bI9t?a{1OpNt=_k zRCl&ge*&soQtHp~QH5oVwy7xm!ZlqatvxKS>>_ZMztA^><%d;LHkM^ht+4z=pCVwveCOPh2F1unG#Bsk`eUY z&NX|WQazEU$|>*VDLR=iwRwvMCb#1rBzv|9I>9afcvTR>B#ZqXR%MkxbxH>EyAM$O zur|f7l-z*-wEAjsjR-#1Fta{{9_%cCArgn8dmYT~lK-fJRurl1j_tpgd%H7LOCniHSP-`G@@cRem0n zpFhaYZ{=q-QZKPmek2e?;z{`lpou2LLY$CzAqh?<$Vm*8pZ@aGSAIAoB`5KM6mT&= zqUxv7ro|^o`hVJ{>yj=owaPz0(jRD>jzKRc5tpA&rMi#h=Y9Ekho5Bk}(s@J(-kdp_q2odXA<> z*9JxuIqD$OM)KNwI}7=z=t+x@l=|^x&q=5gS$@G?D^#2!FYj%ePOyla#BFZ6Bf0EJ zl%Duc+q`Tw$w`cH(i4xkS7~~yx+E58f>Wp@JrU=c?30j#qZ2#iCn-M>$s%zN6Cd#7 zAXzD;AF|4%f(<4lEO4uWC&>r@Nq?Y(!xQp>?Dub#_Z-RlAV0~-={79g_q;2|tB>nuh>5C=(`>FIS-8iZP)UC-#E3jhngl*a8xykmB(a>I z>=&0M8sx=;ZC(scENYY1HzDzHQ~_BMsxQ!FNGUR{^#;D^q^!+*XXOgzr9@M7tQ+?{ zMT*h-#>XKKkDkA;GJ(=RvDyNck$CAe*>4tkGT^M6kHBABRYOF^ zDvLR@`C`rMdj>yqw=)<3D?K#Vi?Y3>3@fT#MXmp`UC$KBKaT&?JSyhOK$w-2GuA zS}a9KY>05#msnzF_flh&@?v<2Z^F4BlfTF>XOt2_C^odHR6+y%FtMo=AY=8wV8nZa zWksifTBex9JgJY2(v}SEK7|%y{?YZpqsL9xrw}zF*3d~eM(fEjgzqk7|Lr1s@-U|r zYRM=@p~TXu7Yg|`Yv74#BoJK0Q_eIt#osv`qsimX)MIM} zta6ckGV5}^8H!zajliBRlaB|x?~sNleDXTia%Ij(EveZYSW25(n-WU!;^Rx ziS8WZTlW(Ed#tn;^Xp0PcIb6}u9{yvAPukqDdmf_{`z)6deTYE2I)^E9xO;FUsYIe z=W|sw+u$y32QE-}z>%REs&Sz3p}HFl^{F~IGa5|P(e^xdqZb2KfRfvq4?9H zAF8NY6R>^Wo{|H%@37yLqGGF&4seVyp(ye2Dc{<`f847&_%U`=N#_{qv7HVcM%kVY z#zkG_UnN(oyzxU%2j6~OJJ_7!BxYmtViFG)n+M&&S2-Y!6`D(Rusx+6kY08Yvq4%( z;=zJ+a2>osEmoJm`VsJqar)k#KrO886cHO>u>SV6u7mY0+Feg09Q-Ycf!k-soz1GR zI&;36^wm4+v7Ns94K;fDs+Ryj{-Yg0j{m^ZS1(L%2QkZ?#B9VoOya>J=AiqkPMTnq zUnaO2;NoUTdm!~~52T|G0i*+p29b#7Et^C<^l>2iLI@i12;v&;zyG8iB;DsEW+SPJ z#DhiBK|u7j)PDDFg`>xQ?SZJMJ%~ED2hp}`4<4ce(6fV~EdtNJU7a{Xcu`rFsGdhP z3{Vm(0;NsHE^W!w?(FB^2oA%{U*sZ(z{dyhE^N3IV?oOE(@u5u$0@$gr(Y1Xt|3&VA(dso?s*0{y^@O{y;1$j&3ch=amtkr5+Zmi?O1VCwsz1V$_%qD4och zUKHv#9ZhmuC+6rmye07zlF&Hu9qM*o zjq^h0;9~JG;#;^QW~NhOVhOwtkP|4KC1>YwIpXPCD#1B0-M3MjcQJ-aS;!_^$go?g zLAG=}-UZd>SSasAfWuU+gYPDoCCvS(C>yr5El>vU0vc8Mn}2ZR>1=U$+&~ zEks~kgfCzn-{7yjREi;79`PAe5+UfeXF(KU#*mn<2}CP|fn~t4FQmBE8AnUTQDjv9 zI;JB)jJQC%^X8+s3B69YYRdL4Om)YO(WS_KNuriztDB zO66kRr^JuL@V4(Ei65<1zsNvfJRn3+VLT#agv7;lNIsM)oVh~8gZR7ai-klQ^o9h} z>K!V2L|nSUncTN8A$o34Nr>-jNQf48N@hz4+3)L$h$qRbRw+zpzIB%Ktv!INM@9^I zgP#8M%C<6MlarQ3errfO6!P=0kSSNiN!vOiK(Bykq%|G^vG6SbyR;p^E@}s`euoIy z0YJ(~h;s#?jD#3v0?;!*^h$`HE7NSg7PMw0#QkJzEg|NmK2;Lp*o-&Mf~-#`k z{`@AjhMeNE#KRybEvtV@NIR7N0S#gqfLS`~pp1kV`We_Lfgv4vIaJ_!@`_dx0+krU zZ8yg~C*!Z>uiAH{7XLjj*Tsbs@Fr`Ihrarg!$TGsS$`}L+A+2$okqgD_9N`5?-_00 zO}zHE%;ghMGfURFUupQG5Y@H7gwu2c+HGwC?CMVAa zp8J3B4PCcgIZ~~$KU6o4%j%$`oHTC-iSlRfz5F|MQpapUs6RXZ6EM*?%_3h3)%=o*vWBb%-Y4oA zNtH!!zdiIZY2q_}q0%IJ5eD9kM3X>+Gx?}3LfpT48T=W|%K8JD3Y);v(F zp4u!oBi$jHVD~(mIc)~!Dk*aCg85vu*bMSGgR>+TBPuvVr zH%3JXGc>$$o#Z@`UX*ZE2@4ThYI~A5kPkG&$)=-lK>+h>%Kja zZc{1--_l!)5#%*TeB8x_ms87oBO0EcR;KiCzWrQ_&7%#5Ck?WnJWIL+Ii~tSdWCt7bc7eI_)kg50y}zxgO#oyeiy7n zfK>z~7!g~H3rg7mKv8m3Vd&lVFmzIl!VmI4)3?a~Oy9P`kH~-3yPR4}dH$A>#TDiw zv!s>M3*8kRfgWv%?huiTFP~2z6|!bQJ5HqP3vf|Ry_fN&0P0Y*16Occ(eDwR@`C1x z;vgPBmm(AEs4Knyp>>X)lH)Jr$U?LhzlKsAWd8xbkV7ZI_L2ScfR6e-8yE2}nT!&% zIjcF*8(eO6;K~CoIBrUX!34Zo+@OyUaf9ko6M%xr5ba zro>_-@|#`6h+svR`dgahlUVcF6gYub+PvN}8e2y!>(1rcygT*Wrj{+sg0zGv(IVW< z#goX#3Yh&8H$pY>1Rbbbl~wk*AClrdr#kzClVC4JW*532tdw%?1$-sv{>1x|>~EMC!TRfKQ@Rai#mOK5w}|ET8~-|;|`%rjUjlW2_kV4W>r zO_+Pq&I7)!1UM%_wzY2e1$l1DOq{mpX}a>ppa=vq$svmeK9 zgcy+`gqEC1f%sTXAdhES^%Wkwb_Hqc0#)f54m@fPaMIGshNSf-?O+K*)|kF68k|ca1aXIfQtRFZTNp2vi%4?-dhAvJ zDsBg$?(G27%wDtq7C)qeO&N|MO(Bjiss1KTzMb1gC zRmjL1TEhx`qpe%xtKjyf_L6>gwAib31B6zk`zq&a)z9(7&bkm1 zXoOj^q}>%_p|Drpba>V*e}6BiTE$~~w0!GZ8CmtU$^#)cbF;iyfO81>QiPWM#K<%u z+qm_+it{q*nzX(97vxzEce$O^AE#*hcG5(~rR19!M6Yww(o`d9my&iUoupPHI-Ewh zWPG)>BtynutOlS@MzjU!H76|#pl3)s6oA@=MNOwT@JxjEv)(L|9b6gLx-pd=N5k z7!VqK!yv7C14_+7dhyZCNS?~2k?~=x5|^Q`^@gu;Sli_I@BNd4?0adKET|ik@!y+V zs5g9&7s~yBlg5g5<-ZrL4|3od$B`X{?w9g`qf4@-IxwI#Q4XAntQ@z+ z%K3IbL7B4eM>R%|)nh(Jp>7uqExh z_-fn07ndMw|;bUA#9@iAt!7 z{cqAzc2A4%J$U!{-s{%wrBLr8`w2e9=OK~gTq#*6Ui%7?TwGc0e{+}QG`kl2ak=}1 zecs~33J>FS7?<-a6Qb%$qUf|z6#QXB2JpaY$pgjLo4V)`3o{xVcF^EH_T~6ic&!y86f0Unx z)cX|BQGfE*6tqn=$!!ZqdHh(CotAM#&t;ycBaaaC;A9s@~*jh%k>tHQD9&!{sbiN<4gmAJ@q z`h%Q)V^nS!x~s8(qfSMvgpF63?`hTl15qel;|tEDwU7QroIu9obn>1oF_^a@U~Vf8 zmbC;Mw&w>BE0zA17b3GNw5LY(V(v&Db-HfCJ`Lj^#n?yf^Aw#9sKsZ-2zUweyTdv`G%-QFF!aN{dxY1{AL(HOKzN@hTso`$~$fY26eA zzR#r^?XJ-j@5i8qQ+TI50zSmfSM}|-4+rsb=1^btjFjJHcd5?JNgTto1I&IJ?;GA- zn|mLXfP&Gg17viOLBe=HY`iY#AwGBsD#QY^RNdY)N2@+o0qvk+u$)#XJ^#8Edxmrf zf>Ul~mR>!K$J{11CuyjIg7W*aMR~Wmc^~h{sbqAq+eD4fG<$Ni0sa2mWc_cTJ^`0%8|Ibp@V)L#0?#fG0u25$Jgq{I214@sKbkk6VH=T4V$~{r|#m^ z5s+WZF(qn)hmVQx>)PCFsn2MRZ2^P{LM>7W2V7}OMiosl^k&^0#kj5S(9Oej^AbuP zXY;tl+no47zDxG=_KKp%J@!!-vzk=7AuBUy(zi&9Bl!a2mRRM++?j<6Fpd%z(kRGF zo+4Q9wI??xS%kZBk#vSw;~yeYue|vOr~Yv$6=yf;o?#5;sunb5pO?*fW!G02&jpON zMtG^;ctssNkCifg4x&~~X`D5Orc(g9855tzi=?W;m^>fKyPGK$@JffU&4rH0?hv|5 zDg-7d%l;C8{jq%z)Iti{6Lqhf6&1(iVluW_j|y2 z&t4UxZ(^%Caq{ZI=wcK#=}@KU*4l%m`q-N5Jwid~#^_#x-z7myjpl%nus<8k7_pKr zPJ$SA_tJ)Ss&%TU==I5)lM{G>0kDZ_M`Ubj=UVzi&v zh$34=4~h_?`64>?RC%(-Z?eP6Cd%pZ!wbX?-yort?yCncrB})k$wSlf@zQaA<9VfI zH7i}}9w~vPII&o+vqvyrkWRSW@xPtJ^rf%!C?YWo61#9umDonWFje3BP`NvmFj z;oEo#SB6=jkpN13b5^ns%DY#8_k5fxGciA_%rYDm#q{_Fo5p0hW(LlkZ|Dv0ab(3g z1<9jYl_$1HB`)<;;>iyXAafKfEmK;r=JX%ihtWR_!F+KiM ziPB_L75TbZQQ%DdtH~sr_hQY56U>PoRqI6OL}tq=5%DR!4Vpbm5D3WiZEAQi-mo8w z3fVBx9W16&d~(Qi4)R3J8s4<)e#~oGENszNrhA({(5fFIE4Z&hFPqPy?ppP|Bw$d; zku85rYgmD(OS}gZvald>;^E(LY&c~vW4yy2t78!MBP(d=C zoO|<}oPsP*wr;FJgS3DdrZr(sCI2Zav3>#!t<|cZ086s)?&p~KfD!J_Mou<*OeZx_H%=rRE?U_=#w*c==H6D54jfL5Q7b#2dh7dHEsgw03^hrVpn=F)RH zfigTNm3tG7Zj77~HqOKsp@1#1eq$uN8xXd!;RQvU5zYGz(UpnI`6y&|4ws<;i4CnY z5`}o?bo+8w#2n6P6Kod%giu*MCRNG7v8Au+%Uwy5ulqJm|DK*(52bM8N&}Ld9@VNx z(QzRT!cZR7a5l|5rpZEroxc*^Ck|lX!Z!R_9R)zI)|{TEwlT;^dV!NI%9oxm{Z*QufgJF z?12FU0BD_R284|33uJUQMkCzny-XLbi!g6!w96I5OpfHEG*f!|!$qqWzg8_f!|srl zsDR%V5NinHw%!8#0m#>C4kBu08gqLjTGP`ajp^vBKp+|1*Sv>k$2z zJQeySip3}^?<0k95ewFkp&>*>1>(V;0T>lB&r=t==I0v)Mi2e1-H+!k}(B^`RvRs&#`>rZR3!5?zF7HF{wBoY6CP|?tq*=c24?N9Tbp@i z*wK{&0)wp6%l7LH`?GbB&-K{;+!;)#kLZe~t&Z)ozY##)uY8T78Qr-?6dI2X4Q4mm z%T{92Jxbj^%B{M2m>V=(rdPxQVz?0nF*!EbPpQ>T+b8+ZvrkACseN;9rf7c|ZZ20J ztE`}APz@?SsYBDLZh@~Te*DoYs^s-a>UAr9AzL4deOe^vlAB$^=5+<(vNgfjzHhX9 z*3%?e=YR2=pcTl)DI6sY5%{eB;$HM)ItNV0q$zfB58F@F1v3`C-Cy6|89H56q zvPtj(F*II6b1*W7jU9Ny3vnx~~Zr_?BX zUVf=kX*u~zPOUc2mJA<&6x|ffa<~1u+#8$ajQGTA>Qjfsx2GSg{-B-Th>KX*oLmqj z9E0(3u%V@M&>ETRY+|`Uiyw(h(3iPrKQU}Z3t9J(Q&SuEcGg!RspU`8eS2sAh#(NM zVo2BQpleu=0u6KxGAr5?^gTagw1b#Yf(Du4mH%Lv)94~v2x4w{+l!cTc`|$LXUB+O zqd#Az3p1V{Werh-q5X+`D})|xqQf_ivCjD-Xy%ME^1dEp-SKsS^m7lsS3UftJyn&m zm1Qi*uChRBF;EectKHe1nV2i(X7?`&FsIT#AQt6e|4w(4MMlB=2_(@?nf(#1>AzG& z8vmR&j#l&q&@bDu!ByV<9XPeX`hU!rU=VNRg-1cXHl*T5G7XR{$f(8f*Rh5oL5N7-m!?nRn!s zfUxAK#)@PyeU>9PMX4JhX2f7c3A;V-C@JEUxX3NI=hTg|Dn!HtHZs8qHtZw(#};S# zP;CD|++>ecwKa?~Lf8w{*i|5l_)x~ zb)F0Zi+WKE|0cPW)>QEi@myRQ_U(>DJ^1F#*e%8_TY~1yO}fFR?nZyJfX;P?dsS0& zzg9TX)_VADGB%U385dKPk*mtcRb>oYb2bq_WK-C;Kl+f^s6*z31h6ega>^t@P>dsW z<8nm$2%J9lM~bJ^1g)z#1=&Jsp{W$zUPVgSoJ|awmdJ$+<^v>p zn&YqBHlB{)O~uopzo@NL#ZP0q^8kC6Qw)PaOJp#`5SOPpa)ILIbiDDl~RWmF6<3g+VQI&T82ADbG~d9iv0;pO8Drq=a}#0kFoj>mZ?O8P5?5^yh_-{9wzacX zt(Gizb6VvaB0w_oqcPWMgaO8J!ZV?=uCYWFM>m6m+sPEn zUE|J`!Y13nU4N$}O+n+ume9;g2gbw5<;ccV z(rOTvZhGuUDog&-nd$x(QWy66CkLFn%Jc4jIUF~SB;mML%eem<5!H z;+vNdd_*z(t#C(b@}Y&yn+wBbAIX0BhP`?EGMBl!5W{-rL{Z&NFgOmi}j!B{ic7nWhHq4H$*>M9o)>pb-p1sT{nwnJ2T_FnZ_ zn)zcG0t@)YZBY^M3>7uZz9~9ZuEm zdWrO!j`EYfg>tq~j_sDSMXsc8Bz+_4o80t`ZhAfG^`tks>Gf`U9qDzXFLl%F-1J(~ zYe`?^rq{aZHKZelhA(u}YuxlI(yM4g74PP|@2b?*y0R+hds~a233<85(+vWxu##eo z6a+@bq}Dq^=JaMnn9Ow|N3~v!iu9nE=-bKS41Z@WPrdONq)LwENMg|oKhCE0M3=fY z`&Q!G3N^i7>S^`R~ z@_r%!n{!f%*&N+I6&RE$fcCR;C%D}b8R3N=eaai(_A81|%Gujd_65ky>AFID3yHFZ zvPoTc+Y28NEP&4B7BydHZ73~Y1s@X=Q^=W_4_`VCyx5nhqBpB9n3%Ymr|k7OCTc~Z z`OOZSGq0EKKDl*wM|))T?$>~k-Q4pil&_hO?uv|Yn!6+76NXE}*Q&kw3Mk@RzU#EN zmGZbdY|NznROC!5T(vUsJSmy>VN>eWPXtV35#8xytkfzRWy#K!hZ7Tj%*vlxKZ|`j zK{v)PV(z{aBLRZyoed(I=z_q;B&#vAOEE-XI$;*K$ZmjJSVUWz)gIZZ5wtGf6tqUv z@l}of@6-eriA&kFtQ0Lx_D+vqNVuT!l`8KR8A)PmOGC!zp)yw4n#6J*)E>hK)10r( zJ(D#d{mwn&#G&2ufjk&Z!QBkHpB#qO`rfft#aHtF*gq)!bKxE3>GxgqhW9&ThP*du zMUKb`{u)ERiUhgwbB(KyRST>Q^k~vk2$$EXhlmyzO59qP`fK%1_s}2T6Lw|Shr#CoKXlK2kr_Q zuiMG~>9Tg)@5+M|G?}cd8Drv792KZXZWf9=PzO%KeiliM(M;+!-RQ<-cWj~7V>G(& z*kS@<7MtNFzd<-D9PxsN=GS_REDRbWi_O53E%mJRv+@uhOoa)2X4GjcUixHJi>9jO zph@JBy`^BmK1YGQZs0B%vHB{o*G)SLYd$mQkD_lR&LOYx6N_xp3U#y*6I?(G09Jr@ zj8u|{Yk^24cdMtk9ToVEFfHj`5;VfNP86~}3dmtT3$at&2s_OopVOZ1CA<|w1zR7B zTLMpJ;ohL=#2ln&N`8xTqJpOj)G6SA3Uj1@Q|y2-vdFzE@LYJty6}KI7o4#vaK?TM zI3eHOpjLs)S#&<+zc6G?V?AlzCWN!%$DlQ55a|5qG?8b}*}Hlv=v zbb2HU9A1lh6E9>o|fI@t6j)KEI?nzesFHQE$ z$RCmv1w=0pUBlo|2RQpYqU#WU2KVETH64E3t!y9i<^*9}w) zCMPU)c^O`~S8d)hKGz*H7AY^t^61^2aY#-4okP+^d}QDWS`$mL`78}tQ)!?u+p}dJ z23a8wzBSV^Ek$c}-xpf^QfM}TdgRJKeelFuHlOm0C>h6Ekt#k@J`Hlqo#rifvQzFf zjjioCjuFxx8TA6<81<) zOSQsLSe{1n;izWPnqfwnzQ{Hp<4CQ?Fk3fxUMvI;(C9lgVJixKN z8l%x+x@#3^jnjYHF~&RC$T)tfIckf@EP5E6h0$?G*-Bzbm%9SX7zIs8myg<%?sGGL zHXBG}colb7<++bGG9fb`!oZz>r8mn9v{Q{xY6Q;CoOlmG$_J-qK2YQr7q@#6ihAss zO~T}iYpuSN-SK?6V@(l`5y^g+{ypH`A_S!%D)GHbbO>(Qlidb zZk^6r0|Miw!mxFn8lkt__njid8dxZO`>{QbCpASR%&~PlS(vBQ+PA1YO6J~J2Y~h@ z5<fPBaCP@aCujC4)HT>`15;Wnm1T zjVjiSF*2MPqg?rPIJ632!;0aSVI?jj3r5R%P=Ly2u~-S9bU3hfh%00xa5#zyyVW%T z1Qd-`kgy0>ocNbO&2sy9#3cPJ`^b!wUBM+tv2W+eK`}$+O){``fIwFI8(W!U8fm9M z^c@h0iCezRzq=vbtI-~)xY=&gcKFX?4fc$Ot?8fjB-x80t4e16MQ&k=1uCK8273d; zsu6fPh{DQHCsJ;~_dQasQXW`Zj{SkA9GZd|W?VxJ!c9nNm|<7L3_~=ohA9$IG)&Rj zdZ6YRfntfdp;&Ua-ATd0Kp?F4)ky*eq|-x@4s4{9vOHFHl~yMD9IH>TTEOMBfTCfZ zr3+;DgWpgpAe(f$muQ8fg>=M|j)MY&yisI4+9l&|S`V~mV>oOvu`Z+}fB-R7fRLd{ zhPP`|5dB4pZ(sH9SY=hH=??Uwd;^A~eFH`sIqmDKvB1-v>Bt$Y(o_-YmGrd)o+j}} znZ)R+CP`U~NUQm@6S)Rwaz);>kEs0?`Wb|439hUJqvzWe_D{I8J0xNq)4oMvqsg9+ zs;FpaPKt&`6bq)*glD(ylLwik9H$hd0wsNLZvDTRq{!6E8KE0a^mNyg@*o6suOgsB znq(;A5n_dQWVBYa6Lv{QO)uz9XSDWG4k0bBRiF@0Gy}@Vc3BFP{vGpFM?%nVekZnK zuVtO1#E39n)JG8G>M{4rgK+o~==czIneT!a$dIMc{3oAohl2lq9OTF+J51+S6q)buDgn2U5&~y15NZz{21)jc-4fhDYH0SK^%0# zzI|9hA=ss}zzf7$w{(zmvvlb==cayXjdRl^uE9!j>DHC1$G6)RC%8CP7Fu}_9M@%} z*L+z}r(CRyfm+IunJc}HAgoc6j90ZCNi+0P>O{FheB$BFvVQ<(e1_8?WLb$ZYGc}7 z62w?yJjWKDJ_x{yJ_D$=4@!HVQr7AF$nr3g%#9Hr6Y-*;aYvt__7nF5Yhy(pU0{-% zp{W~bKQXk`1Svh=pz1|otK=|X?t=&p^`V%GK7&NaN)ob_qNJFjR52+{s86w^h+VKE zY<$YtYgN|RNAwo7t;Ivxsh6K72G=$EU=^%ei|>|=C)pbq)S6>ujaq0%U@o*T2M8gE@BL0JkY4*? z${&5c;6zCDD;D~6^N72b37>RDrQFD}La5AfsacjXl8Kkc6NK8`ldsMDw|r{#;*54? zFx?mExi7Z7$GFbyWeP-T{|oWUUP7?zD7k;;Sx^|ORld!l71qf?rO*|mID*cUAm|+n zqOr_j#xTH--4ryAgcR10RbzZ;z!H}*tvuy2e2O&yWlL2x6!Ki!i%yKBwVx1VLKIIdYQwEmUPrIhf~8< zDg*&J=ZB0(CI~~|=7lJRvt@+}0|{e$pw_{{AC47*Ll!F4?;d#)MwL^g)SzDH*wYrl zrA^=$=|5a}6qZ9f5FMmL3?XypJG6*-Pyv|Q&Thn&8?G^NAvVrd_6Ek(+5RldWOVq+0o zRctLBtK$DiPg<8?3~`>&0`58va6-Ae@F+4iC#Q;E9h+(pUG>hNRsPNfMhB;QjH#52e{9r z?zLF1jUzeujvYWFFO!Wr?apUpeQG~7f6X*LXdH&S4qh28k?U}Tpw;^#moVg2fO7My zl4eG;9Wt<_7uIOW$4WIdnT)aSGJsK1Yb&SIASyu*Nx;BK1z4=0SaS=T+?OkC&1- zMcZCKMVk|-<446cUrJ)j6kWTiX^J*|PXWo!kq#X9E@d;k8l!aWhFN6 z-v>h>Z7#O|S37e>lVSG>8;XUy_XA46bSJg-&H*_&Zy zkMBmi67;8b-nK|C6t!>UYE?3jM{Dc>cvxW(s5+Rn>^>C6UIASQTzO$1cpFlu1ES0tQYOb>9bTHRLdc(3X=rB1QL2m)(AxlMQm(6Q??VcfeuakUZ-i{`GEi(3Ft@?LjzO}?Qq9Q=28C&>Wosj~AHw%NS zgH)K045EZAc^)>;RNgO~$kO7ERTe^AT@{gIXI7A*D=7hk9I~};g76gs1c~M`;;=H6 z2{5%cXXLjvt-v=b6^-^8HyUk1_n~NxUlbYY(|gYxYz{3})=KjdxZqjko8xiaD{bkaS-2T}L{2lvi2&mWsj zOXDq&Y0#Wo!vzeA@Z+gUCEg>0xo%k3OIw{CT|x7nh3ehhT6xFlm9O9n#uu&Vad(93koa)yKu5p}I2b_E645R)Ry73G+#45@3o8Qs8^-8#Jd z*!vZ7kI0zIGN3y?#~Nh}CbkP&R^madk4gbf&nGDX%+;b63YCtz42`=~5tCM?0H31W z>=N1>XY>$$OpPE1!8~*C7QT$uNJH&w?xz`yHK1^QYSgNs*?I@fZb7N(xY@7`=okt< z*&C8-`Q1mtM_owso6!QlIlj9PFkTkc1y|liM77WUMWhsUdtHv9p3HhA%L|BqlvxaS^iU%nG0o9W5*H}OEMHiLFzgDo$bi_RxjlLK+yX&B`P+YY z<>6xW+@3tV(agxh?UD<5_!-wkk{w-0-rto{8GaQx_TP5zWB2A-wP?>d+M(QSpm(D^ z$*Jm=R8^xotg4e+l~`G;$tBmj)HNlrykenQoH-<-pLsdM?g{M zg`9@!a9Tj1R-CroE*99u<|f;pX&VASL|y!GRw#pJJWBw?t3YAUwAvE@mLmY-wPf)M zfY0E1R{%u06#?+={zj$&P?n09%VyR{PhkP}^~hvq05ci;mIo9?b;R?XM!3Y!9z4bD z4x1A_WdlB!j&qDU0n1*9*iNi#Mm*t`T>WR&ye>GV#by<@gtUMSp70@nzrCG$GUGZf ziu9INJ&U1D_(TqiZC}14zqIO$$wr@wpx5SA@X)TD*XEY-%8~UYX<4sT7m=#uY2rxk zT4|1(?U%n)>SVU&xRS*GhUQox{b{I}F3-&qpiGdh7*#Uzf`T>H7l{4#tSE8Lc%hvwM|3}-|z(-YF z{Xc;Kse&8dqasE{n-pI{F-nA1B3ZbLyGT`h57qiwExte&Y!wV_B3#$4X|2_#)jqVf zEw%cHZB*+olz*sZ9uROKgog7Yc4Pwqb7jgAV%mhEzS!K3hC3P z^Ag)w39y)$k zlIWHywH(?!3O8nqZqR`KAMwH4YodL=6j^Aoqq zVc0+4KR8((z3QL-{P!8Tg7(Dwef3(rHxtLdn(`*|p{l+sA>HB~b%%!4VYXI>i}_fw zhxGQrO>R#?^Si~}=lVC3z6D*R*cybktSsJGiNZJEd9qHHI6q|ev`iC2(b;~x zk&K0`WQh3d&12@k2=gdZ)NlEmBfnJt0o>}^pDT6DyV+|%!B)Xko$9#Q1!mXr|HOXX ztYeqQ9(!#!r9^G_s&J20%(w}B`icI)tX%jE$%W5={{WxP=lP})3G^h;00oZ*83>`l zshwdL(800>Ct{B`iH~lcZdzgH?-Wce>0D|aGg|={QPT_ukuAd$yA9Slzg8-)zv3E* zOfN-nyzjEzT{mVliJw9{HA%S(%x;n+gkCqBq>He7DLRyj1|toE8CE4?$5Gww&P?31 z8IJFH(2X58(L82XS7S_*a~wIOM&rLI71jNdu92(|h|rXoZ!7XJyk8>F{#5(DjM)Zj zB+NDjW$h#QhnHvbZ5kv9Q%eahzyHm9^B40b$9njTO^)f;S}J4bl$pF*_7gI=$Z5{L z*VwP3Jv8R`mik_MVvE0xPf!`LQd~$nb0Z^yxhYKDXr(Pt?j`1QhH`ROa)k`AV@l6V zcc^FSm`5am27T(h^ZWd~+5R`2=eR1!H*&q?86#LBTH9Y3sJPxgH=pPwAI%*Y^i?Mk zJ-H3W{#4B87@WLIi3(^j=4fGCw@|smmecBd^$!7HdCJ9MrqP2q;oDL;H95o0tTXS; zd1?5!F@p3?oo*VPIY;l(mKeofizMn82l;$Vu>#J@`Ar7QLoyo8HJ8@Q854J7l1;0t zJq8m)F}|gxEYP`_%$uwCvBr5^oDc5SVE7RsJ&y6eQB|_Es%~$7m=V$Jv8|dfiXf@9 zQSOLK^0}SZ#C~s#rA2{3XBRT!xYQiHGkXRfdsH2Yak+_cd6(Kt z;e%!Awphd1w-HE*fsSyjmg=6xVi1Or4aH3E;D)tM!3M-)tfKCEgle%~BHFfmuscb7 zM?Z`0YZ+c=N5?-z{}$?_qOCMp^(l z%x}@!ijn8IP2rNf#n7QUB%gd}isZj|-;n(H>!2gS*$v630n0)1 zDHEikWl~QxA4lTOR@AdF1vsnu_9apqktTq-slPjVwTk-K*(iwyA^SHf8&%W{SJwPPZT+=ak8Q{sS0h7!jTiqi}u zyKmNW4oW;hLXQ$dxfLZ&)wi*Q#fB13XyMIy?;?=jef)oc{4e%TA^+GKgZxt=ke3^R z_~W<0IwL>cAX{CEYQKR!3eJkI{_Xpid6H@qG#%j*dHrIWuWGnhrsR)99JAq+}ogdVx25X(iDKEbM zIPtQpl?*-06-lXXRcfA}^Q8M|Y;4`rMlLK$b*w4x7{Z--C4@7o3WWO+upA=lk_&`L z#eK)7I+aGoQ}w+jd0l8Dvv10wjpXYiX#xmsqCPR_}0)`?VhB(W)=IdH$P`Z@4c-n~UEzv^nhxDx6XqMghw~n=@F% z&z2)1&S50(%z>}mVCwn)&%JssG|!p!^t7%iRLSGOFGwQ}+-}nPixpHd-g<0%Z3hI5 zfOSR5G=wbE_v=LsX?)U5eVGyf&9qNS0tEL{eVvJ`O=YkA5tW5qO%J+AurF!czkyA5 zt>K=5`lwUmu9;{m{V~VNo5pT6&zY4@HEtc5rW&`0s!QYMag9IFtG&7>TdJ;hsJ1>k z6l|Sel0pNF=@~3U+iR=wotO<(xN^`16l1Atsq^dmG+&!I$BnFTsq*g}S>X!|Czc{B zT+3_{4;i>q>eXhg%~hDb?Jxe+cjJg+UukHL+@lC+VHIEPyQyg`&Q;T8AN@+-ky(ZS zUQ2z7V)fN6^@qNur53+wTI%J?XsHsfr5+PZQ_JX0#Y8#x)z5%o0n9e?=Ug7DqgG)& zB&gRvHjOgX6+RFn>IUj9 z&L4s0=%NptBZMke4IpWHGBs7-e+h>#yFLeppUFp^yY=qt42SoNdK|vNJZEya*RB7T zJiTsx47cjmCFYtvnQDM1zP4u8jM^C=EAH}|VjHWone*E7{jS`|p4aXXDZ4eV-EW^% zQ=IUIX^Q(~qfRNi#lUhj#r{7qO%XaWvne*c{^d>aedyUq*gU0=ItiPbe{7m!rtLMw zCFVJ^DN@4bKr&4Un|qZCVRMg3?aEk0KvDd%0qyiV-q7zk37!tNfbP@mt-=(|cD!b2 zHkHk|3(y4(`@Rn>2hH9&+tBQ={+TqJukXKvefOa%ooE)(N1bT)y=x84#?JO=_HFZ= zi)L$ilcL$>N(Ie+Xi|%2(5w8&c6!~xsX&?hHpM*u_g(*YeNwc0`c*@_^DoWdx4FP_ z(C+tR4ej1JJd<{}>H9CC-HK~+&~7vzb?*B4B*g%Iz$}k;iL3RTNjtCWKSV;W>wlYD zb^YVaHM{HYz*XsMcm4G~x_&b`rMmvE5!p>~NmZ_{zpL9_|G?g&su>6eFQ$CpLbXk2*KSl&efr%xdtO;xhA`qbUv}Pp>H+ zAdQ;hH|Cn%6nC@q^R=7e0=b@DNKUDyxao`z^Wz^HK!YZTCY1lg_T$*c;!=w&Zq<;? z9`Z>8@VA}(?NE$x#~p@bg>=x6%mS$ylDQH9k~!fvU&B&y!=YrQ5xV|P^ZuC0un3^(Yu)hk)>+8U zDn8%QdvhFkjf`dZduQ_9fGiFU>^!Ji)q< zlR9qb5zy$=IgjF_&b58>a#Pz6XLz+;Y@Tz}_BQhLYMUgDYP*MP{B@*Ds z%`?X5nx^!~EK`%h%zU+depG%+m|eNT(BQ`NX*{OhW=tLjEQc@)SVE_w0fS%K%yb+{ z)hWdHY4dxL3)K2(J8;*F-R~4#tdBB}D|F{^uI;}BB6Vtudnl)KTZAq%Z83oz5N1Ml znt9IA7F&4XwZ&v^)fP9KYqp?#74LwrEhsl4BjXR6obaW2E)<MP{<@JnvzO zrZP-f*jP5Yq-yEtQg)!Zdzm^!q*BB+9WjD$qpUJApGUQ1$XZvUul_1cj%{Mthx6i> z>fW`G*J3@wqJI53utSq9>i5pZqCNqr+o_Em23vhOIXXZ6t{FkKEMq3z-!ZKARTJ*j zdwXcYz598?XUF{j=1Eyz9AKT8=M@*2%{%- zbt{gV=2d?OF{g4@e;z4Q)&H)(qWa}r992B@%A9bR@K_rFo zkGs{Qz}}0yYWXKgnWDf@eFX(h<(f%>Yw*yPJd?|bYd!f%fdo^?vuiCNy(FO>M%MWy z>-^=ggRY24bJc~NgM^K-2j!M^c_vH5-;nqY+{f^|lR}x+4Ih?a%{JRgTp{Z>g(86` zQ0OQXI?5|_lvgMMQ~69sz=oklE&)5#FU zdb(3HSBH=`7`KijBpX@E-okP=T#gKR4FLf3Widg3BOh4?Qax|>LyCOtad zl&-*CZo;oeuD%%+=xOe=$aMo!*hOo=H&NO?66)SKoNw;4O6#94ZQg~32pmL_O{Sd? z#l`36Zi=s0`(GOxyKEav(>qOTKb-8AR`N}h_O9DAjTW+sdyiSl|D4GzQgUug9PYh4 zsMEVCcl!TaHGA~QzNh>TT}|M!6Qlk)T*^EpsXYF_2pP=RV#C+}l$o>EOLYLCM9Wns z7k?@jznhD{j7U3DX7LEpiGE4vpXILJE%TuC>pzG{U+w?$xn#2>V8LP|o=+RYz?`xZ zZduexzZPnaGn~9*6-Oy8G66ntIyHwUO@f83u2%f$Z)kp<7jv5GmLz0_HGJQE?; z_+9Ih;XBc{IzQYNzlZs+PUNHWMEwu2OHTUuzaTr%Ch|_{bvJK=NU@4v1Sr;>aISSi z6x%_IUN}xlV8M7Z9vsY01{O9AC~;oLA%RiEd-r>id+PK+Zbi{!lmn@-(uQoRI05;xotIywi;Vb?V2>@uYZ(!DU67_0=!@5`VT9>z0>PI zpQ&Z!M|oYdrmhQ%q;th8N>%&RO3>N7G7})uc@9dZfkqEA4K&^I8c5k3OIkG$V@w@H z)jCWbJmamyq}u4jfB1Ed{_H^{xj1`iP2swsqDWr zblEly=v!UECpO#WW>HO!=2Bz)@1%EPxEkHZYLP^M+&?P{*nHhV_fbR-Gk7G3X7M^gsFAaOoXh59wu-V(DFAq@ z;sAPPX?zz0OV<8uT35ZusXvBr4vXvFJEO_zeM%4ZGUC9AC083l>RZ7@r;;jW2W81A zvv!E}<({3i&Moxt46n|KO?2&DOt%(lx@GKMFH%Ku>{l#GBGF?N`)=ux=bW?<`=IA} z0qii23Udwz*0s{T*SUC{t8}N#B$CF?2T!X9ci6!?$NAmaZtp|${u}P-&TKtrO*hqf z=_6HSYI^R+&JxndB)5obwa#P2w>UpP{tJl3`GG0=zR#vaxB(J;DiUOPcXo3{#|&%K zx#!2w;dszrfJO@+$xeEF6}TP%%m`02#H77D_dPYQeI=5#-^UE+Z1{KAG^2|ZhS;8@mu-H-fyl3Fud1d zcn|I3%aw{0+3zAh=dlHly(mrgXV0L%zXg_q?7uq2b=7bwC>|Hv-9X|F!#m7W8cLI} zVxn47&sik&xbAsgc;m%bZY7z6T+<$&M^fD^4^LnHJ_6aVWW-PC(%sWn{|+5qrX>2!kp|R<>GFKlp z3U+Gafc6W_+Q}YuPpZqRU3=75Np{>$e$YV(Ts_7RZz`_OhIr%5b0+b$xkG}p5KDiuOtwyu$da!$*u{UIZV>%#V++0=nvZAA}c0OK+Ok2c~@rMG$L%2q(LHpBC<%aNDBB?mER7NC~cpZbH8puPz*YGBQ_z)(gSSN9gr}KGzXCUeL!nLeCKNe>y_T1dVir9w_LTa`s;`(`CKWp{#mVRE-Pa8j3bS+aB5q`3u2MRi|Bh-|1 zfnMRakr%Jm57CP9;y=?5)2qBVd-3w($bszamIyjPACA-AS^62J9|qs-l)n-5vyM$`S%wTZ~km(Z$VkrNii*==w^ONbo@+#v*KkPp^6V4Kd>Y8kAm*u zC#$+KK{s}UP8D=zN9g5({<|X-@7uiipE^P-1iiZ>^e91p+7bFKK{>ZN8#Ap8#6umS z?+AKYN2s)=_|YAqj|;kQN9b<_{hXgHIy4BnHX9nA9M=>;TNssAkhnFYqlfPb>OwM~bnSA?| zWODhd1UG#xncV$)GI`A#$>b-ilF9GBl}!FRS-L5FR^1kVYJo`e-pWuBfL)`{_mv&(FBPgtJ$s7<63R)(hrUxKb_!H98z3&sEM})8~-)eEOZ0ORPP|Z zt7Ve;wI4L&mEU1ub$(uLb!4INV$Ym9f-y^t(8h$`DYKMXr)(lqG0tDXncN~GYxRC8 z3l)#NE44BHnW`t>6+gWjIfSM~&;w`6Gn%pDxVlo7>K4DU({;AF#{6@*pXp;txkOv%o%f|k5#VnHQZ0C_&=01 zR`I*@aQ*ZEUV)yLhSc0 ztj1R0&;MwtDFGX<*=}K`J<9YpGId|EvfTbkdidqMG86Z1EG~E6ydJpXwe~gTz0O@D z2<$lk9xFxSP#D8B3-w>*eDv_{WMX(mdEKu)PDsA*V-_)d{=G~D?3z(vy17;ziRcf@>!!VeXndCTSwJI*EPS{W9nX1<6k_y- zCBWnT$i^SNvD_mqL+GhIrYT(pGl|qCt5BdYO(5Y9(KPE3$fG=MPO)7Sf0vxPPhB}i z%p1-`Im$V71Sf6yhY)#&pA1xw1J$?dVdPT4nQ>)dVNx-)>TWN+F?^`o1T~qYePFIgYjCbT z5PP4+nJst#5Tf<-#IZbMMJ8n5(Aq;4yU#rPtY{URx(b7_pB9C;U=8AQCxmxT;v*s- zJiKRIxt}Rm%PI@xrg*G+=kgNMGkda|DfXZ%D%Lt5o-TANcA?g*5neNonNx2WkK7$& z(P5tHV!N{+x763^%atjyhV_goaf*5~g8LcDq|vDI@bE+g#Owxv%{ov@D!c=J@ehbN zof^>E$EMm6RKWWK(Wf+&24=L(<3f-{LRC1=jRj(_;=Br?GCx=DiNc0)P==_h(jD06 zW`YA=Z^b5~fNb^R%bNIrXDVK(;sY*E#Rs%1x7JowR`s7cJX!@`pT+;VnXy8TQ~Wq9 zdQdukoEM9eKntc!Hx@>g0Df6vyCr~w8IsqEne+%E*2^-a?^RD5#4i-$-Pp#{R7opA z8`yk~^FNk4Kn!Ks%Th$?ctgqoaf@0XTQ<1K3CxiGwg4&A{!%SA(oGNo_^a){s zg|B@x5fMe*z$4iQn4X@A#erafNvTmE4o_i_{_s0+5bR~bBW`!{^&!0J8!Ek6BEw_I zodEP=+i807CUGV|>c;;lxF?k^#{C1{^LM${c=v}e9Cki$A+l+xJ-&&nHa>LoN|yWN9=DvT$IMRd8rj>Nxg<;(*}r3QTkDkS%HS*`DEp-h#ozy< z#+tFk#1TKtUf%ZMwr19obT|s(fqm86eNVm|@cGX#5mNsmDKt4ItNITC%!%nG2DOTx z;MgimM|FwmUf!fIty3!aV7jiciU+i#T573ar>mq~PF3K`q84$t&Br;2)>a`ZRlGai zMdjb-F&EJ$XQ1-AQc>)uy2dJw$wcL`KXi@CP@$676)JugmGeDR%DY6x;Z2Gv*C`dK zT&ruWV(xJnR2c{=ofy^U0<#%)=kLE+MjfGw_A;pDp!ubzfNQj3;(0F3N1MkEG!Mn5 zGvy*+>@MD>DmzUnsqC#>iQ6-`>droM^UZ=#!%q5$-P{)UJ-N)pEk6L|WiEm0wr0SI z)XK`+yt>MtaZn;~%?Eqm0@ zq-0ZpMRuK2drV~4xY|&JMbL(SqGBm$-;?v3$un_4YP@n`O~9Oa<$FyZgDO9O-YVBs zt?uCCbo}&*t%#3>i}b2(e*AYy{|&B6)r5jtwOK`z`85w zy53bGKOXy1#jJax4OGQLZ(_e&pyuEKN?rRBD{P@0NLA)Q>bP$NDG#Am4;vDh@dP7@GEh-%0<&$Hzpd+o08;iw3K$pjpK=cwIZyJa&@RO1Fm> z;(o!YCET*YC$G z!i!}do|P!{VqZdDty#A@F(Cpk+2tB+J^snE?~HRtMjk$Y_^P+0n%#MX zBpt@RX(p7@6(<{#)Zz)Q#vN-OGfCn)s<%14MF1}v-fX3UfOqH`tH`Ic^tg8>C1sIE zSfA(uv&mB|oHRmq!;sPq^7K_f0}W(=hfyhS3L)iL#m#0l!|)( zj;lxDF;@v#ae!nEke@=q+&mRjH`KQO~4XeNB(cLbV3h8Mi%0OxkeJg^n0~GU_$4vTq5~WoXazlEa#jRA#fYQb)e!!BK zao;rh^8e)EqAmC5kS(hLesf$j)yS5OgA7K~jsPRV#!;gdXR^x4R_)ym^&KUSRt#f7 zQG(-O^O#w41V?-<^dkr#XM6N5^vyyxXOdJebC7i9cTC3XacdPxpEi$~8G9rRQ=&)G z9i$QYwka)J3$^8pS(1VM{k|L|eb5DFcg)!UziE>G%Ejn_!@(%!E?S~;a*^~6R_q|D zgKw!wy2w0c);uMPe@3n;8MRxfpwmvS)dZr??ytTlqfHRPUS7A}H(zGtx}ANeT9J2s zR_tJ%@|Zq>ZzLAa0yS0T=)RMmN}M!bgHG!CFNp#3*^H8YzAv%ge7Uow-p|VDC|V!L z(W<*jP2KuZH(|TMJZ9Hzi5c#HM?I+9XKK$9?sXlYI^r)z#H)0>zIAVoc74tTX1D7+ zfZuexJ}vxX6*sb^oNiZ3<>YGDxUR6%uFxE9c$!-e z3$I<_bWEpucs+BqkadOK1a8%Wmntm>Pwk)4g2lhe(Ski)V0H^`zUP~7!JWq&jLNa= zrz1_?p>ncY&>cp7%erA*)8Y}qw9E+8b*JlRM6=%t{!cPcP`5bwnC%4 zNA@z%^Z_6h@~}`|UOg7o?GQhyeagQ2P25)5Z|7)=M~*WHE%duhai4k2M95xT9Ss-x z^L1Wnn-c&9uSH%Usaj;YK8aPl?8|78JARp?MQ(P1*)4J@!0z^`E+a~t4Z5y!>kdFy zyEr{@FgTUNj2eJG`9%(#-f@B1IJE-oE>5`?=3X}obE&qW=#K{)5?vs4j`7gBLWSgF zk;9cZS}_QR6tPGz^O)Hpt}9uavye9_7CA$yV3F@}t+wCexWsJnovm&~ywtZJYlK0U z`j$0glTHmWA@@$$4ojHA<^hOsY<1=!T@0FwVQN{asXNj86n8zIC zn@Z*$`F4^<kLB4uM+I{HBS~5ha!^ zAkp(P=O^d4ug_env)opj^%;f#U0r)}YBeHt3TXI9cP)h8Ia^*4)bd6{lku|+svIfR zJMuGvosUJv3;QOg;$-Rkwu< zM5Jwbz2B)>8!zYR(kafk9hxhnCP_hLS?%c=6|17EP2p078?axp+>?$|^^2PA1y1xR zv4K}doqATes&)Vt9BTM?HJmkLk=jw@xEh`kDR8jK(~^|^M4>KGvws>Vl_K+Gww+N{ zF;AQsTEeB>7Z;n!79*<6NrBdUbf~2M`Ff}>tk4q!y-zS^qRq3?nQRmi`0>;?g?xUt z4TjrHY!Rk7y<2iZzVVU+0j3e4QN7m?&Jk|hBp6*$3?jAr? zijW<5Cd^SGq2j1TIW4?NX}tlZqAf=2YT9T*4aa)O+`rEGo%ixLOq68quLp&U^Q*^KopKd`GkBYuVeYak(1B@)S*qmRsff)pND|{PoG&;fdZ=Pvm2%P+!C?C3@bz z>0<%*g`-9g{8#hautV$u;u=m1}+V3D^wXzs-s?(aZd;?isP4usYckie1Aw z#h--xCpgi#TFE$s^;FBovF9c4UF{J%1cfeW{DJHj1JMwJ_p+?7lYke#o74k$CFEeynJqz(A$?==AL_>rV4 z#2!#(*PfqHL1#dB_&lDcYSHkQV8dX04BJ%cRVyc-kT|40z1bw`!Z|cnJ312j*onVH z_TXz(E}t9`0zyP@RwNF{EKl2RZ01he+`UjgI8-iPsDYMkm7D5B&(j299PLD(ZW&QX zw&^A!%%f$)Y~x!kj=he4OG;30%jUMH_Maog&@*pE+AejjyozO;1zcooCE_+RA*W4GXcWep3>^EHe@Q%Mw3xA-|`*jKb#me)-9Z2W1_ zvDk$~xkUiqR)7Zzm&B;Gb1jk5l*&%MXEIC*`FPr`y`SV(H5YJ9mHrAeE zHtbf8EcZ@DVl;{BL>fw`>fbU)l$s=l5l5EB-#478!yJ6|c;lf?HF?g`P1VuM_?iG9 z$m>CqOM=nsi>&6&1v+mWn@IRDvnB8QxTl8w#;Kkyg79y^evy+n`&U)IFq!ra!H=88 zt4`u(_kDHcyOaBcVqx0b<>D2gs50SDR-&g^!-V<^hV%B(SQus7R@d17TL~kJOX&HK z_1bo_#y`i8kD}eY!w*?@cz)0vc0GQs+mj2SExvPp`|3ErnBda|w!;}kh3U17L}fklB*#&aSsnl=ZIrR&mgiufURD=4Bk zydGlKda*$bU-VCW7c}|G{7uox;{vf;tU$!6Gp=7)V+NKB1KmjMzFg z`n43Y?j1_En4KosqAEV(=S<13dC+lhWwC)-l;{-uA>bl zjXWgaO(9e*QXN9kYfxTpe7O-d4MRsB%;I?LZR75`2A45O2aoMk0y>XC+GL04fk6l*_8eRu8H)c701ZyJIv*`hVI zG{I6UX|*~lJG>edFO@#akM3Xli(VVU3xf8o@N1~avd>-S&z4v2{ts+kn$QHjNs?;; zv&TPehuZ@79^+x-4E&TLH0f;d->+s!JG9&W+1{oPMvS7sFn?kAgl7s!AlcL98z$yd-uRRh=%ifUvLEW^cg-adL zTBv4Jj`z0~MVfXG8d((QKytRor~U7rT}4UG!@jJVw~Q2d-BSZwM3yLixJ;^uH2)>R)azdw3n(szqQ25k$d!&w!Olw+pq|sy$nZreYR!hWi zj&8B%_L}Fy=Ib4QycR@%d^?xR)h&@GJtmh%7U@cgHa!aSmPl*rHr*!A?U%Dn{%)$6 zyG>3yo@_a)T`SuL5w(ff%WV(m`)qKVJ@OzEqg4#J*C9Qc-(ht_^K<~WtE>E|ACvDQ z=#KuhZDcW?Y}$5owGkol9lC9AkLbuEjmMSEMy~lrFIUr*wbHa~^f$fGJx|TV^Sugh zTvkTzCJW})ja4#2SMHcRC~t|Y?Na4oRcRRjFR?-lPUt-rvM(|Lt1sUq;!Q_4?SO(Iergm$RTik+Qr{HC4)&ineilvct}n_243#^65+#MBpc|IS%W zRAIzZsDabw`JSW``b*6A8p}Qi0)8B@U$fr|M%}n3_KuMKf^*!v2()OpE?|!;j=YD< zzxPGZJ^@#;g_2QBZeF$DqTNFJksj?7rX|z6&smfHxvi4%Xx^m@y7VNYk$SNsJwB>YB<1a z;f4XgmJk)&vmnySmhOj0*<#8# zt98yEjjQ!RFuvvBb&$dBv@x<}|n zc8se%Dzf7oU&G6!^@^%l4f7c>6IcKDprZ|R5rAmuRXe6}!1rXEJ$_zfeP+{tOfB7k z^90ytuj#VJ!V2%mZWgk?a5k+G+Zn;-8U@)V)fM;PpxT~?K7P#oSfVb2gEVtC1K^yu zgnTn^8Dm@Vagdj5rN7QMYa0Xwbxg12;NhN?nZKxVxvyT^ZR7J9pYb(hmn#iWJrjr$|cCtgVOR8iRMAEv=NeK4U#eZVO*O6&3vl5a^2*943&jo=gyDaIgPJ= zu4)qv&4G+_+r=r-s7gc8spSf5B^5Ef_>fW&!`aSOe})e2W?PLz#L9zE9aV8(!(D=A zm~hP=d848!(&17Mt%e^%5e2SVlLA}_z?BHUqAYjLg*ZqDL-xc$*4)dDl*!yHTO72{ zy$}a~ADV&uj0cL3gxvdXYt<2ScP+O|}e` z!fx{z8%%YgraHzG_$A-0)2Le1^q-!lJ0TNN^=s$<<7Hnix1txOJHYGC=5^`-TLZD% z29}#1kbFfQplWvYKr3e#FsmGlOmos*Ahi-9gG)O%cCi#z+)F2se>K-`DG=Az!1rIh zytah2_%*(2rVE4o99Fr`H|vPc5JJxpNQii^c{|B0R+0QdmwtEiv|tH?l9S-W#8D(z zYu;3>aT5$zf*DG%oCGpmv)*D*@ik0?4dTDmPpN+7XQx?c%e8Fg)n4Gtv>bVv7r(Is zkm&FP*w7D3OIC8JCvino%gjR?E#t<}oq>cjz*K4M&a!BHXbO(V)cbjt>e?xZ;0Bw^ znkY8*gcc2}!#Bsp=!prLW&V${e@b8%6?ajmF#ZrAlP=Zb;US4@03;6Jr;2fs07TJS zOP!g@n<@IwxF#kgh9^#Ub8k{(o|-}~%^;{d8Oui7d+@PK?eKw#QR zCvVM8^ozCtE%KnX-Z%8m5)GP`-LQp0*Lj;@oE(4+8kgy@<_L5t=6A--@Sx@{)%Kq> zaQf~QEar6y*NexcaE2@j%zsz`uwhTW{@JVsJDO9x%ReXGGZ3PQOJjo1#|5xgl~~C z2znalLe<^fZtAPwmqf-V8woY3u+r>D{lwjmdI+mt#vj{P{{d*>lkJ=JHaF2K_(WSg zI{Ie&?pVm{F8s3zq@i(;)%l@Z=#jqqD@biTu^Tu|h0bT_Z~O)t))^G5nCmt;x^17- z#d;mf#wFlQf@dz~x0-LfQA*4CNEf?vvXi z8|LxU(x5`zhDB+}UI;)-nbQ%Z7zk)vQP*r>6EY!ctO-^$I$Kk)F}W z9KDx@DvvBLzy_LLI=}`*^Yf;oDDyQK`(dUyE8WWucsJexr%)rp-DU05`UWb0X{9@j4MNMa?Yg)M9g(QW}UX zWkGh@{g}|5O^7DUq3A9C`MxJd$f3e8555zm={N0PDlDj|7RKh7@~MD4(J+3_CPpvs z#L4Ur55{6*N2mAge8ygBmE!z(U&!h0RQ@V;`T|JEn5*8<9X+PhO|HJ?4$-acDpG2A z?x3)LzDrB1YZ$KG24%h4ZJ}d$(cYjZuy?@umOx!J=(D_&-x>m1qt5qvbQ^TLGgSBX zqhc=jjuqSJgOZ~?AC#Ew`Jmv{`LKd32_nr!nAe8Iwy4hbW?#caY{5?S9=tlyLo3Pd z@c0_TBOjQiZdhiU{CxG=6=hY%ipUVtA`ewh)AYvvvwZM2Bkx&HGP_gUg{rZIfnlLM z-yN@$EIOr#=??;qd1dnk=HI6bj!Z8~KCAxlZ~Y+4#C^4WQ0@&I#tKG6s~53~rLFnQ z)G>_KqFZp@e2SJVW*Aq17IC0Q*X3Vi()CrsWH^rt)CFCy+39r@7H80MHYh3A_qnEm zgEQj4{~mMDIajkxZTdrc6J@F&G^Agp_fn=_UB@d5@78XuuJ=>6uytI6c~#W)_^lOw zgALWRnes=Q!7q=JnAx?HCqg;inA*gP`(5^i)tu~UJ@dDR-9L`%*jtrE8f~^JiI}{` zH*1Nk3{s?1IQPuLxu0~GK-oT*<@W%YEGM7vb>po&!^73k#5;quL)(NVf&iCU1l21m}sBa4Q zl!KV-!N)>Cbo`Q(*73mGV6LL{A0NvDiZkFzs*luY86@Et_1j+$Uu zS`Vq0F}5?G+`Y}LBQU-^$mXh4OH2luO?Kmv?MksyYMXLTc~}V{`dsrtH=RcII)7F2 zSjA@v)MqzbL5ovyMZ3!FtH0XtMaBC{p-!Me4m2rTy+k$o%%tgcDRMNWkYuVcQcGNQ z&5UO){yOrZS_qq6$ws3=xI>zm|Bc(7tef^mUM-O`D(WdUMT$GHnp3zwb5k#%)V$=l z64G6kk#2(0#VW=LVKiRKAu!kFKiZwV(~N2aU+~pGY~bNK`pzx1bo|deZ!?qrB45J; zZjHaVhYZjT%tYg8zR%gcS5bG`EY9w&6r#@>P^LeTk?pFx69zf2d+#8b_;(vuGX)0e zSszlSCO4w;qn9%iz8BiH#i{YleC~U4rAYKEUZg3Z5!}@WnNVCv9(Vo1!`oE+TGE0z zxGAAi850rlTUzT}N7GZjSpi$*j3C`oqfX-qWDFvPyejTEUTrg?7xQgRtEQ%$_`4Cb zU4eX3W_u{-&o1kNSbzJ}5bJ5ojwxdO(tvUk%c2O6SkIG8#9C&OFT91iF!U9xG>B;Vx>B2QqI#s}3f8qJGYP-|J;`kqUjz6fTmRCtCM_>J)1Rp@Vd#cr9uj;<~ z+m%>V-;{-fuYQs~Nu;y#knq)CC-}#m;d080ztI_fy5MpLcTq~`W6T+UNyEL$yH0rV z$u7KIkqVDVTyHY)RI=Ae&|Q^m$tRGx`zqPJZgNj0tGzs>#i!M=vypQ&n-`kqYLd5; zy7v37Wu$-N@C0r}8I4doF#aHYK%puH_jHTXv%U^bZ*Oj2!<}B2IeaS|V}jnKTeS9# z#JSnIFHM}84R|6kI2-VgIg%y=)q4`Y>=ZLojJJ*wGcc*20@v}*Fy9irqd(Md5mv5$ zY|Y~K^~RLHChlPP$Qbinbcne8J!KM|T&R0uQmV6GQZ=C_KA8-4v)Hvi{Kwx9$?^RK zRp(cw{8NI_Z(C8^Q0%p#vWl{AC0@N0&d~V9@x9~avrbpwZNM>#k6h)!+dOy^@Fw7`9=yqe&jUUW_f3nH;Ps$Pwfz8a-NU;E0RqY|nIkUZm;!}B_4O+~829~@FB>#teWSnTl zBGXBzj# z8d%OTY?GFELHIb+J+mry6L^?Jd$6G@QSEBkc1?%=1MhOk1L5=1k-6~s)>q-f4hZo1 zWvVFGj66&zcrxTo`dP}!cpb2I9}VHOF}~cg&*y(E3r!P>B-%$>(R~#N?<_i&n<<{2hbAt|oX@c= zPC3g+V?VFtCa~d$?ffwq#_BqELk~56Oys9u!(5QHeo-2aox*|}+BFo0Pn5B2u zW0*}3RusVPh0gaX_ap>CMJ4#4Tf=@( zGWpoVKG0{Yxrd&E75U@i#EDt>ThW`L=Qh#v2Zf1)NdG4LRTAm_0$MolV2rhKpSF8+ zq%VNm5x+~Fw@younC(wuo&K5h%8ZXW5dS9=FFIIRq5zpojDL1tXkFz7$h^ELjC0Kr zOsl?zPa$VE^%i@SYgj5uzt`TMJL)qv@jIN%{%+)hGYMF`M0kWJ@vo6D(c6oq$>S;B zrzUw>VaNQiOMY-Q`az_nn7Mi-4%#e!)GSWRr<(YLtnxa3UlZT9u`@pFfwkk)|3Aa$ z^DoC|MOJw=@hMr)E^#_(w|NpG~@n2s43i56= ze`;T&FIsjVt7UYlk)@skInTv&7L1y*JMLna;O z?hpyTn>faF|BUp>WmZ|U6{|gjPS+|ivM|6oD8X3YV#=(&Bot#Dh#sPC0>Rk%g;wP& zldlTK`lHyaoOF(555Vt3nB+GH?0qg_bI`C!rM2e=qVzlMXwcq*-d5zyk|N%LYDU)K zKZT+fZwW-l>=+X}E~$xMK$hWtoE{VMjeIL0x0fD#AGDY9E`hZ@ zypJhx%nm^-R|I@#ZVi^L3D}ba2vt5G@}2o+plo%>KAh)ld6zXl;=o)S(EgoWt_KZH z*X&{lR(|Y@Jfaz-_U0_q41nd_7#@+dN!g-+joQ7V03W|CL^<+Bw3HLHn~c?%DNWhZ zko{(0`^rH6)@r-8*(uCX+ULGv@A#Ug1*r^Sb}!dm$Oh|dd7!|vIL$g+m6+@z)G@Eyn}wg z^lgY5FFkz=l(9(WSZX|$eas(Iw^p__H{t5jHu=Zy6mAbwxQ|0b^CP=? zLjm^oR2l74ySsT!Z>848x0)&G#)3fpO9pY)CFt6CjIyc0*U$z|OdO^UR}l1kb$o6L zxLMZ|j@uF_Yl%Nf5=HE1n@qqq#0o@u7sdC1Wi^plmiQeRoE7UoP?jRaNi(}P{Vb}0 z8yH$?+F6y0eX}+x5lXJ6pc|=b2-i+~FI&j)N3i!APK{h5!}?%G%)w2}sRPX)_i$^_ zVm6!RV}{eNlx4LCv#(rDd=L3hS=Q&;P@-6|X);IL2S8W~YM-F}mR0r%M<52uUV}rX z?`Ql6Ry#)+;;&{89K1dtShgCqYC4{XEbyBhvAdLS{@ZT8`1Y0tZS7aWvsL;9qi-*# z*i>Ox&_wen&M=fwN9VP*`FU!iLj?uJb$le`@Y^t+jXCVzlQeDp~v3nWTXcc>8f%kMMPgk>d z$$PYTtcu?KHd$9TSNlfAtIHgFWMA*?f_d_f#v50z6wV{&Y5%I|e$k#~&AunkEs704 zz}~xkjW?d<@2y-hX=nbHD!X^x&Tsi5a$K~VvJnSsb0Fj$GkEFdo7ms`UO9S2tpqJG ze+K9o7u06$>u;?cWjw6eZc=-=Y3JzpHWb)GZ4--(EGbG59(@Gae~kNCtMd?M@=g>4 zk*Pl?mtr=plvMfdeNJbbN0wxga4OD)Q_ueZCoMA`y=KOWI%;oo&zh`*+fZRKZY!+jJQb#qTAU+Ye6ED9 z3-&W~YH=P6$pE`cqb~8$EVU{c#>C#@%zWFB=lxeQ(Ah5+I$u}@$82=o z2l%zoVf2z0X&B9e_)4n{K352zeja(IyG3WS(2sNBbKZY|Pv@9Rrm<4k>@RGt6gHSL z@m9fa#L8b2go`EW{{0p&q7^UT1*ykBo5#$x4!qwig}RzR4y@#TCMmd1skpA;%8VF2 zQLe5!2Xcw8ews!r-;>jdLisqQO>C1wm!7Yy11&Iv7iVCyzUy2++HDONs8Gb4oeO~d zCyinHP)CLh+P5jZ@F@dT{b?X-Vz%-?A6oMi>%3b>xxAWL@;fdpy+|)?mI3=uu;~I3 zFYwW)Z+2`5#*kA1O43Cus&Ptnj#iqi#dmktn)4PfQmd(#nvB*u7jxBIIXLVpyp0}k zr^T5f&|9IP34iN-a^ETqV(Iy*6+N4Y%V=6~5fZhwEVI32cB%Yi>bI<(*jczaNGdN% zSa^d;y@)N73Nf(Us&sEV$y4S=I2h@ud5&T&43upPp+$8x+aC!>q;))>Y%C~RL3_P1 z+doT{^oQ=ZbL!=&c$n>%p!2OW9)qjtTI|PU=wRbO zY8FbspdFf-YTv$0_n&V!O29L^kY*EvNoQuiz(8hnr@sL5otJJh6LvO&YVVIv*7_zTIpV_7(QnqIgR`JtR zPWJf49!5NGVp23dWtB|Jq~Q5|gXeZ<1)$FMV|V_({x8rp)+_NXlpgBZ)lNJapqO-J z+Dog__Q)!GMZkU)lO+0!^9wh_-+Xz7(u+=zef-<(Oe?{}MHiwaoqy28CWd=>NFdo_ zHgvFSwU9F>*iA?q%9?MCj%MFmLCeVg0`gFT^$_+PS<3#|a($xR<%b9JcUbnwqUzaf zI?S@6El%Z^cn@M#ZR_LuDVIBMK`YVjXH-Ly?w7BLcH-BXcyHLN-Cktq2kGV&8q#eW zS(3`**KQuB#F}_HIqCaU`npc(ulCC9Ez+f}&EwQ+8Syu;Gk6qmH%(w-D9$Tr8Eq)A zZFEU~xDXI@pr=JgmoF8qTuVjGV$T7LWycR0s>auIjUrCe)v!; zx!BM=aj@aQo;6*QN(xA8eV7p2xV50_`}sz&tO7l zIpp<87LPXsS%wQ$?wE7~D6nM?O@Sxh9XjDN5bJ+vAl8>q2|G)Ck%u+MvtKa>K?b9z z1tJ^v29hhtXYzc@UKuRgYPBrgOnU^&wpo=cd^2Z}gZ8g!N@r0>Tw4l*gghqy$-gwE z9YGRhKIw3ZIW$n&>Wh2_v$M(!GF!a^yFX3N9BpMFb~6n5ycKz`Pi?WaeRU(lb)VV! z0jw&?X7~~^VULdN?cU*-_wAlrNxmKT+Kf8KxzPancT@Q2Dle?bgu7JA& zbJvTzUgoYhcfHMBAMRM6SXvmU+#3FC{91CSF%d%&UAqD;&wWgtt@#`H=}ZDqZV?4a>$ znkMgy6F26W_(via_KwK9b=V%(vGoPV&0euF`4JqkB6}~JT*R5y+Q>DXgz>-eV?iXV z?DfH{urS&b6&%V>@X(?v-~I&$@e@3V+oI}2OAMH}SNm5V#658CfsZioaR$ykaPEO$ zX5cp&IQPJBvLYYWL6d^;{hDHa7h`piwf)V^@yC3*h2OqlK|$Q^-{AGYnCjuN2vPNNqn^@112YyF}xmDQGh-jl9SK z6DMogaOBd0g1k6F7XDr}nQ84S@X^%oRoSmpMczB=ER1bT!jB>bM5FLKZJla%notXt z>$kdm9kp5)sQfg%Hl)>92Fi3WMx7FG8tG2n91BaeIF+yftB;NuLOd*Ivyzs$gIGH~vJ!ygX(vACT- z7`Hp~N1rWx*%vG*h}-@9<}tv#{9#qT9zMQmX+cpQ13|cV-pjKOO)#0dfC-GESY@h7 zNDIW^%0SuHK>qd&IpJ$~m4FHHjr=%MUP{@GUF&h9k&hkax8(*Lk&W1l59#p~`{gYP zHx{iQ*a+1F!|Dgl<{I7~3P+aQl4a4DHm}A12rpTeOodWx@wW+J#mw$drz(z+j6He_ z;v&&6HJ(@ze=DQp$2xW}HvSRkCy14Ld;_23L*pAfEZpX+moHh=KbwubMK~ZT2WH@P z0qnn+z}U)b%-~#0NXJs+C}RevpWJT^n$}qR+dym-<_Wp;6OwEf^c2frz8i>Wccw!l zw(x}P=_ULZ%ZwDv-^Bn^YAKRKwB~-R>{G3&{d^qd8jr-coIUy|f2}@>R{~D+J5vuj z=Vqt655h?sUoa3gu@R!xs>J@p_Urnw^sT|m()EwtRALRn2NEZV{H^doxw?HE{;fZb zg!T%D5Z_Q@BAIAwf`2|fAZ|vzcpr=z*W$p_DNod^eN&pQ_T6rrG&yaouj}v5e1{bw z$huhcsq~ll>J?%HJ0KBAv*tV6{cM}BIl0W(_oKo68ETtxnZMRl=QPysLe>asN3%ZA zGvIsd1=Q*G`Zs^b`oMB)CCdx_&#@{C)=XlyyOLoq{65?G8<84T*-F*}*3cr$1%6R( z^iQBlRVDF+4zc@hhp}~IiG*JAxF>v5*>b7Y!OES!Sr>za&QJ>un>gRs(AVHJtx*;? z;^W{h|A!YPCW&mNtqA9u?brTRmf&hj&H8TaLfYXL$4h_97H1Oa;fO%xn(%r`Y|@5r z46)d7F62#of0M(s`Od%>q<BHXe~owlULzK&wLNaxZ88wkNIk6lMOLifoM7w(;;+3w9k&z4e9hgrNv&R@ z+7L1@Xb-Y%d1KwG>4%(CG`YwKWfOmks%3)~yHlmLdSSerHjQ(qL60t1roHfPlO*+~ zO3U&4#9n%jE-07HeC5qX=5Z^HdIXhwzI zg439BcQ97WRSEkU#43&g2R(kVOn7D+E3o@co9jGI!qh~0h&}@2!MaA4jF6Z$kqcY= zGMUonr;&Nm*@{bBFm~13S^d|@C)PIBJI8z|Nvi#D2(4|-;sj#%T`{ZW&k_lW{7s`c-sP6U`dA zW2Up{7Gw!`ic+PyIexs4By)>x zi;-gN@$=Z*_t+w9!Feug_Cn-NJ;cgKk!rPkXQEEG7#a9?z8MoM*dtn6vEd_}+o;i5 zvEW1~ThMw|?4a8N1RjBlZfgZ&9Up8=A+@cnP@aJDJDgYyx3w0xcL9wjxw=3oHc*>N zgE&i1XZEb)GO%gyRXojwh7;tjK?t1b%&~8v8{qZ*F}4FWVHQo|FSX{gp2CVpd_3Y# zoOE9cZKVB@qCaG3SY)-ItrW3-hZ~ZFb}*dokSXbOMk4Yn((pdQO;6fky?wvL5@Pg$ zU=+{8(S>F$CWM8*zJV-)8WNWW%U-HI+w6776dW({hUFC_PffhfmlW?)gsVK|Wy$+5 zB=*|nJ&NRcFC*_?68ObX<)T{FcfUKeZ=iBg;gsH0(KA6VRJptsFO*5n)P4BfGNl0U z4sX7g!C$55Bi1vX@aYwdo*cw&BSe&-vdw|!y#-lp@(JU9zJ>F}tRomhX#hkt0rkq_ zTG&SZEw>i@)n&FG#0?ayEW@4zt1!;4kP2DP#Fy{3FzmYPui}=mke)7ccI=cX0rvfv zjgx2E)kE638|%stSyzUPX{jD!R1@8lK-4_l80Ey%8elDYp{k2H68d zbU6#OgFUZeP*se`l0ZIt52+PY81d{laxa{s@vC7`#|$Y?y~9%K6S!;XKKq>gCpP>0sAOo5f-YG6M@BBhi+VC+B`A9 z48(yPZ5)VBackb#>YkrzLDwj;H zX(u@1@+h}(S_ef?Awy*SXpZ-=VtoU&72|E+DlEH zqb~RRrqr7GYF5sh*+--_1p%=0d(QnvEVFR{pG+ z?~|az>6jIFdHf2%>gLCiU&nZp473(y%r&`^N<>yj#kuI&th^Jf%eX77ca~~aV%MOi zC8V8Y*mqC{=7wTZoj`0flG)!#Y{~5R(w2?>HdA>-78eJ~)|x31T${Py5Qq95y?l2( z!RQ{Ck=$qoxSIQYKYs;)(r@1&vze!fFDA)fYjE+o>k-&I{ko?$e+Q-Tca(KBjz?@M z?0-%mcFIw~{Dh>aO&k^WBG_++>~+q176onpKOr3mRwWM_sA)RK$wA{)~*6O6! z;Saz(wF>pmmd=M8!C0V1<71-ZPU1CP3N?8%c<9ZXIhE%KiAZ8ADo?K56RFk9l%lF^ zUb#gD><|*lk7=Nll3;q1saKTd9fZ2eK(<~)>IzxEi)^a6c5GEz3R|~0p^I&SwoajZ zib2^}M?u**4`pG@n-s>z=_Dt@5&u66;%vz7upd?YpmJy)WOpfL@<{m>kuTUPTP%om(fa*=rI;rHPD#QWu}M(}7%J zBriMo+oNzAx!UW#k!q74Y?7DgPeb<*c8LSH>)EmYBk8=Z5VXIjMVxO3)BCXt;4rz% zH#0#N@xf{(&tK2qsELi#_7bwE+orz{A3)4#Cs;Y{e&Qn%?S>fx{6GGF3x(V5u*P#M z#i=@$5p^rgztTK<7^;YUQ(eIDFXYWYYSP8=Ibh}C(}ge~ZwUjhZ?t!>TzR7&gnZR* z_l+!eGUcZvVy3tVZ$^ib);!0hhpBM}4Qe-wYIj_Pj6)HV^hGYuslC=%B|I@!d+|3R z6{7xsCYu_XFxA~39+%!f0MvsGZx=|Ko*&p(jkVVtWpjyJrS;BUoKNWt4S%M62{|ux zeGLqtG@B&Zs$iMi3o|t@CSqtXaEev-rtEQMTEl|UUH|w3Bk+&;>A+v07m7_|pu*(3 z&6Qu_X`#yPun((JyeiKcj{=NUr1cW|ATMk@@bH-UAIQ$|XBYg_?ZL$5fL*Em8}Lh3TW@BWx47NEgBghG z*G>!gLui!Z2H&1V)H*X`({=!ZpJ^ROVLVHXD+Ldej3O> zm-U;MOrXEARtA54J!NTpDG3eGJUwY|AqQ7{9i%j7zyA9L}@ceFCvKE5jG zUHm+J$%3)#%Z0famJQz;7mJAu&>(MwslR?(?G=o`W=OEuQV~6u9xUHecSzU`@^t?> z^nNp1dY#{N#Z;5f<2CJ~p^yahEXx{3>)scE^3M2np=8 zYiN4VBh&OKZRAUPeU$AggJKsP&}|8ljrXs~GhPk8M!t2FK`C_pA8qFXA60QZ{%i;k z1i8UyP}HbV6R`?PloZq~WZ^FC0xBr2VyOyZ>jNPRSOo(~fa`TzYPDjkZLHeThyT)F zxUUV=gkcmxWSDe3 zpazq3q&<}Nr$$qV*>lb4WDfcXfdkTXV43G*p5f)@ZfWaV?l)w#4=<#oKwr}fLX?$P@6hScx#_J@ zxBm2GK5LKl_fr#|JuRb4AeI$*`~~94LLRRHBCL5@n4}+MLT}*Y(iZW+LXnnfdR8q) z57{Kkuk}hvE_4ArSpm+t0NbE<)YLwlT#^Ndr6E1t7!lxtifY}veu#Sbio?MpNur)r zC={i;kIS2msYR1UBY3b_gStb>xUAbkNXmMHH1q+!Rh(pX8gS1NDNLg~P!8g52dW)K zCUl1WOU^|Bdr8|>I+BBn-Tt?)woI$)Pf#U|Kgu%B2qQV5a%b5>c?;rCZ9hi!Zvb&r z+RKzryyB_bC;&Sz{SOzy{dkh!BTxr767J>Ki~dORhg0`QoOk=870%! zE?7%AiEy+bBSvM(*#y#ahpcjmS8u8K0S7;|83paGqBic`jD#g92w9E??rcgP22-S_ z7b7nNfwPLoER0{rQ}1=vbrLbEgK8rx7k0gz7E|{NEz{MHg}s&{xO@m=*EI8#)vaNe z79{difihEeQg9ms;_}R-1#ZD^8x(k{RH`j*x$1Dz=4UP00Vm$ql5lj_rd3RuUop(R zTKVsrz6|u0#W*Y4`jFsnHHfs|P(7Rx)2P$9Z1d{i-*PNFd^gbzNP-j*j-yudmMJKs z^~BK}Exf5z)zP+N7jKk>)Cp%hO%*3LU#7gQ9Q$NTVuUSrZC}qONDc&e>|>|5=gUw%_wo3p%I~TPb42C4KW2rj?ulV6%IUS&Gzz9i-?^$V{X9bEXS? z8FzJuc^;Lvyo1ordU*>^Ky4Pu&8=mUd)K$xSEB(_>B>IJ*YSIxdWCRNGcv^-Dq%-& zYePK(<`A8^eM3ucRDa607hlWV!8H&!l52!=anI54D@CN7j!`wi>S}mlqFbOEM+HxR zOE0`iJKEmmlO2&xbKC8M-inDI@8%1G)G8Q7176h(j*#NWjlj?y4e9R9Urc^yCtN=H zBy8;rN5}P|O=jv#Vd-rTaRXo-a0~$;yf?1qr!Vv;PL>tqqtb2QJJrFm*}CgJwb>p< zJ*w{8K?@fq@~}s`#Aj6Bt2XksF}3ij6sA^tyh48|Of6+5i3^w2zEcsXP|I!^M)fJ* zw503oHavaD00~j&I@xw1uC* zsS$P@)kqT5PYO~IFE%^d=zZn$CQ|F-e|qeIritVRbr@_D%_HHyr7#$s0^a9jh^>JJ z4s>hfK)113EIOq0RcN%F=Q40z)t1CHbJYRv%fFJ&fxT2~R6iw6i4NwV$9YXG6^J~W zY$JC2RWZ+Syetf*CmEAA1~oYz!B*653U8(s>fJzUqcbvMn0P68N;pq&ity)%Bvt!! zyfrJ4nsuaR@Q=FlEWeHX%J}Scew}#bm4Ec^ucJA|%~d>?QFV4UhUxuQ2Y+l3IFMnp zXXHvfL!}2Q&Pno7lkQn3wY1S>$uY=eY1M?8xXzrr; zqYO+At`*6^scSAPug-aljIu)IQZsBL2-5G)d_635Da_h%ZF1`j#sURWF~WQ|%#)BK z#6lz;7oZs*)eBKeI>O5lNyjUUnw8{o9)_)m#5D>X>ziSxO9#rSfi_ZB)IJmbqM9kU5F9VnC09*T-=jZW>; zaw*qLD;nq<4yPh<7dhSq5jmf2$N7oMgJ?j$^C-ZQ;6G^YqP~L5Mq#fb4YJUg%6s|b zGWAJHp2i-WQ*|IwabYoq49*pq5iwyP%+=*DZP5Q9wO&EJXi6WhF&E4AWphy~Z)s!2 znJxWO+6(dRs=6XL^pQ_{BXTna*#rGcp105vz)NqsP6fwmY(eF_<^@{TcWZfrzFOJE zbxF2TsCalR&bZmklB9pKPGWr^ehTjt6%!pObFvn@x{L?7jDZ1(1)fVf5sj7UN!7}1 zN_6@{!TZrso17(TBh*Iv1c`+0^SBU>oFrPo_8B58Tk`@HX&!*TN^7kgcknyXF!^}d zenEs{1!8BM8fJ~3S6;ejvJu9=11qh(Fjrn}NWz#&j#VaqoVT8mHHIg%$pta(rYttF zoEM^m)87sIxZwqEn5#pWKhh;P=W#VINDr2@aIcV^KqwPZi?w!DPmZ zPp#tJ^z?X_BW009yC(@jKlzh}- z_##`iB_(gVwAo0*-%9qh&UyO}Vh=gu@fv{`({LdM!^wimlLcMm!T;P7wZcyi0ZHK} zE#JB{Dc@S>Op`Y_GXP+1%7tCeYUwVerQ|)(uyz<}8kj)lL^be0MPFIZhn06$vOMr1 zi*G5UWu0>@iL%mOs+9%I_Lx~-mB}o^Phv1X%c!0K{SrnYlASI$T)M-kmNScRFFoyD z-dznZO4lVz=A(+Z;n;K~Nw5}s=DW^wIz6Q*DQUH&9f_jks^P4ZGY@6ZGnTpLx2F_{ zyxm`DA|wE(9&I2ScK%kUJ&6kEmmFV5E%*FhdfqDtD)OwzAN^VS2dmf!J6yn{gy#!9 zRG!$4`f`^AZt=DspsY$~+)b>7sx?Kj3++<(?dYc*FXMwyht0BD7x}#TY~p%9>m}xj z8ce=x+SU$~fboq~>XvYCoY@KfgRB=(^jvv&-RvuJC~NJLjkdm0Y??3tHqI z);?uomj*WSCMoDTL~_wNri6^K2irIELifm{Zl+)TE+vnF>K0geFc(ocaVwozP%$`Y zBkJ;M1%}VirAhCf=#bpfNd?MRfiZBkYn3aW{B%k#>N8kD5}nY&9Q^8vG{#NQQ?2nW z;#Y9NF#q~`xOfK=hX`dP_XK2^_JO+eWkG$*eH^-CNMB878jfn5VhVPwM)jMXrxm4} zjcUdh3h{G&e~DY?R_pgw8XmIta4i+tj)V^;&f|Bhg@M!4;;pD$^+!Eb1gJ2Sfz*2` z^ylkgWANv)Vrqob!hKzxG5e4_^@}tb)pyHAV8gq!r{&ILlb|Ld*P!lb5Y{UoBNT=< zC6qGTW>3!#_MDzqF&g2Oyd-s@ws_XWmCAaDoHU$X)K<|Lw?bo}qoimIyp`IKJoIVv zgXq(uZ7MW|i2W}nci8tCIfeWL&+1x1>lKBpX}O`&I~uX)P6_enX|mu`a6yqwbcGMj z;!W!#9rg|@-a-_IvtEVp{YDn%JNx;#`33M-liTSn@`Q)?_f1cD`WpwbaX()(P4Nkp z>jZe2>XUSTy+{UlPs8yY7<*&RSpq@sZH>P-n9DD|MEz?g;>pJ%SjAp}g(380 zp)uY=*uIY?D{ANCvN%v}6x*R^Ag&p7&U0(HcK8 z?0e0X$=Q#-Y>0MYQZTH=L0c$j5Ef$m0WF{1MNm>oKKqlDAcJSEWR%ZF9-8kWbx`1+ zOM{Sgt#qEHH`#!PM{j?YPExF@p;l;j?ocad_t04R?gGgv4{CwPgL{Y)E*7E~#cL|2 z(aT6C@ZsdMG%UOGtL!g z`5GFhzqf$Y=q!8bk zb7%E7NJJ@Y76^f*G>J5ERp(qN#nN?`J|exGJddXMBO}-)+>@n9vWI)#g@Q?Blk!>M zTz$7xna|m_;syT7`}4D`)y8^EF!+s)CAlf5x5uq8LZ@q48tX^l7e<}i~#h*?&%`WR=HR!ZGAk#@*5^Ks! z_fA?x5zWWrB=4OEv(b_Hpm!aF{i8S=7B3#@G-c|1uP(GRj!@^BePvMaudPq$djAme zFuDE&-^us(Oc~jpYM7Z4srJZwdn)#Nc`Jv^V}$y)Vq+3NE)y_^+b3YgAiHi4?2EWg znV@m>9I^Lp%sv2yfm5VRaN9XdeB{D*2-d-VR47_#4Y12=bFIKy3?Jv3Gh_3lz-VNx z^Ph29S(m@`#Ycx`oPPP4{eL_OEf!u~{xT^Qr(eENg1h500Cf4GkG|MgF;0S)jhfp@ z!8&2zRe~3s+a8uh4F6*?b9rF?0DEL@?f^Sfi;=GV&fF<7WRb>I1(Nj&vJQ{layba7 z72_a!>^|6Z@xPI)1+wu?kngPhikXmv`~eK{wjhUq+&oHwohY?fO5QdrJO`&dEnuVH z0QM2#D_RgGXygj!k50z}w7MVp)bV4>fHm3}v@!7t`j*=XLEFRaip%*T(gec49BUun z?mqG#4+(Y~*msECUm-O1osNkSEPP4I>8l)Trth@PIau_kW*E8D#{U&na{n)7*{XCI zmjwcr)jUBZ>+?ive^&UZmvA}}H*%i2NwozeKMjQgEmdJZCin(G8sC8P5k&&uypb#u zd;XicYuE|)sJHn5 zzQBG5v*B`XK0)lu^ppc`*dAYo-czU;9bn8T!W^2yLA5VdFD3hMMj*bJN=hvxJLMF} zC*jhsjB0TVIWK76PQT*D_p$P z`GdTm5`X!H#>Mbq_?H~NYecsapTMp{YP972Ir^x*hU{!`eHO{YWk2vZi3x0@wwV2f zXSW&1iFq=<>=S3MypRX!P$`RAZFV9>vX>$t)8I@Z4dJYc-tr~&83OlQ_6z#+^wfuU z%ZGNm<&2+HniYc6n1x(D3zhF~F)?TB&-LaVbN;QKTGNXo{K{Alld{|zx5+ns5yqfTj@v+((#2)Y z!rvcMzzn;m_ZD^tMIfZc)7^i>Xx}F1{-4OyGVBp~*2wNGO8%Fz(8{~>=Y;P5%IW!8 zSlqTd?ltbp$scd@&1xUb$XI~G-K%+&_^u5wliH|=ba+vKNR zD(tlDRmIlGx=U~7|2(Lgx#n-WnNO%_re=-;Lb91}NG_W6ZwaF%^J?7Ql(b(S%+SpH zbYgllZzb{QHFFATWie;6s##h)BMj$$XT7c3x#h2Vy)RPH^md94`8MYcAS799yCoNG z`bgz&={gN^ss=gN1$k4OAW!jteB1^38wnL{Tgbp%%v6xun`9k1Y^<%Osto;+dAv`Fx*TLVuH7ETO+h z=-t*nmM&9#AHhCj4Z)CYQ@~nz@=#3=)SN?>A22lLxLu_=t zoQ2{)oHIs&n(0=htQ(O%*rBU?gMW6TQ2>59l9%&|Xx_9aX(t|)HI-Pg_+ zX^-x%XkA~gh@d~x2dpwp{Wp* zj+R{X)3t<1Ga#<9(>aBkdMbFOR8Y*aN?M0x1ygq@>KbROvtqU?b>klef8d!D(4_C+;Z;YQOfz5 zh5Myp1b z^J~hHI!-*S98D86IYmHCj@Dl!m-I3rBv?VPCuCSXj4&?^)+lxo#NVm-4dGPew>F5W ziY>0Z1ol&}S2P10Uodo&(^IM_Btp=!N9N{MZC_NiKHK7nsmsy*cFMwFu$syA;+_(2 z%MN_PDA`l;Lq4uJb~}HT34@>Tsu%>;+mR>aOjc7DmV23oiax!!q|xilCZNvVqgRR{ zCCd`|IPuF$3Q4T-WA+FCQfbWT<__LXt;spT?T5T-S?cNZqpVM{r?^)R{b#tvoTwXU zQj{zs*(}E--cvjnWxTT0`c8en72{X3pxy5Q`<=dT;nygQc^r^#Kv7&*=|LA{F;xmF zyZZDpJQbuGkpL96`7|bpKk>d>O;|-5eJBS9DSA1ry%(j|ckFkpZ>qh!v76{=?}YU4 z)$Nk*yteFp9^T%^U65{jCj#=nZm;U!k=KMV&nZ3ohCAB5-J;G?T^pPR-wI;|GGds{ z!X16`gy)x`Pe!!6h?euD^r!HDxO}vy7I4lrVb+_gJcpb^X6egQPUI+K*?s0(lXC6y zxjEK=E(?7wi|X+Cn3@lIzrd1f!Vh;-&ZOLbaiSVdK_bwS-NKF+ffg}@xn3>3QFVUn z-<*~Hl|!`oc+Y$l0CKc3?ehGupyZxc7&6?Lj-uSb9o)#varQ9~!N@3mLPIHxz#tHa03`b$($D9|{Q`-49RsXG+tn#$KPHK>b0+ZMv zz5k@YpLRcO>F-9q7M5%J{8IGPwb%GDAZdM>YS(!8Yo;}$wUuU-Tc0L=*#wAYqg7|n zIBjb-#ZSIwgvPZ6E);xK$#mJQtDkWLNE`pO^%`Oi4Kc@+kBm)NuZ26JsJ+#gPn6EN z(?n)Y_AoX>1i9m4;W@-5QX-O{h~y=HwMUArT8r3q#XuMly^sdU=Q^s=E}w}6Tt_bZ zvx4o!Nfqu3+QFX2W{?WXZtOG z&xSqOK`|S-v%1w~A68tg`K4)`yf*oVXQbw@95v)FCQk^ZH}7-#hunzu6*;Sfu>G7o zEX=X4)_HA{tm8IXGw7xGH6aVAAUU)LzUpfzwCTmeq zPw@@JlJ`@l{q6qEzT#sfhM5qV{F}{4D{oBv+_bnZ|G2dBLq}HriC=$X`GOFvu$QE(!&R^x_Q8;65n8WfHjL>6AP+H z3iQ*QeXOXS43UtMd@zE=o4`xz7Aq32(8lm0(tjEAMRY~iPyAbv>?GqPCz{-379yw4B=JM#EiJj<*o|xTL^RX53 zmexTd zU{9!a4lBxjf3vh0JG7>6O)z>LjXESIn2qXAWWx+I>OU>`C+2r|Y9TG?pn4e4w5uWV zp_qol$~dOM#Ue^le!W%xm_J{A7t?M!5h5sZ;{|Tm*A08Qq3MQYZaCTvN4VhxHyrDR z)7)^P3USEA!R>%iGZ_Ld%CFi+AE&hTt=LZh{f(LE`j^o1FCL!ri zFaz-yJYch%-sGm^FL+=b>5XoBotwUpbo>PmSn8%Pbkp${Jg}DZ1#Wt+n?8?p`~?q~ z@21al)A1KPu#z@Z@@}sCu2O~3t1E-0Z)ktPbQsGsw;7jvZ>C1L>Kue7;?MF&M6j*X zwbqEpYJ633jqJ!NdgH&^-wsCH{Y}Jx!4QemhVZ>9o{dah!^iufxg-4RT@@@%u98`$ zmyt`7VkCL|d8E-ln&Odl%KBngU2PV>rOe8DnGUbWe5uNp`LfED$nT=DQxLajh);<< zxuMZM{ms%t#+-eUPYhucW26@L1!|J!vT>SF=^(~Ixp8(4g>q*nl6cxs=yPg5=B$u0 z5Cu#TE*e1>Ky)F!g>2l@NX^tR^>VCWw44Va+v;~_Q}8O`V)Y`gFeili3P*1z!?MTR ze?lR9O@_A9R&yT;NBdWE(-yMF)rluEI&g>RV*Fe>-ns%ap%9ir8#1&Qty4ym1Zkul zZQ`}k;~r#KqL87!PmHBIuW$)`7cHLPL5mnz7qs{!ZTHTfw!T-Wkne}(#>xuSq>Ys+ ze-L0zk%bTjl3>gKI*Y=cx}xAtqvoe_5KL?Tn%?U;R>p5&HOOSUeG+?ZYW^$wKV?sJ z<}O*}O6r`Ia}oEbyYR>^a&j&As3H*;jQ(gW;(w!6Lt=wRxS{EWWp3El4STs^ksB7c zVZMa25Y8Pa{4(W2O(x38M1#>)wd(B(lNLESI;l=4jgX{I2B=)ybdm^FI`8TvO$e_T zD@iZvqyjbPS4@zkr8-HWS8K&YNqR;njn%oPNzy|)sZu3PuXJYVc&(1lbtdU}osQQz zH|h8`9iQi1t>anQ(nRcV576-f9j|pR*6}hOU*Mdj<70Jvp>v#$SL*muCtJsBReVL6 zRPp5va4@2)>U2_(Bwg?%I4`MrIIn0)5A^NJT94+!~!?*O>fRZH?hf^SmY+I_a_`%-%b3JH?ho3to0_AyNOL$dC{$>MW&m$&YKu= z6L0r^KEh3W)|+#roA_sM;%GPVNpIo}@P6{S^uJWSw96)%)(seBK;BWWLLPEuXw6uT z!dJat6|_+o=)F$r+QipAykCc`TaN5&5jeO!Rkj_KRc&I^;fShG2#^86=r3e)wZM8r z(1B@<{AO6`jVWqX9YG97T%8MA0TCQ@#6Wtr0wP%Jh=KHJ1w@eA5d-Pl3W%V=BL>nR zAm48pn{ee3LnvznVeSz_C~pO!?ua3X0G78E^Ntuos1=0yM+{*^D+skm3}Iv|2n&uF z!su2I79KH#8(KkFdSnn*vYkRSBE$QKkUXf34-T2r4x6&iu{>z6Qsr2Gmm-|4q^AvI1)SSejq++Ouc#=Z49Usi zyxCEn5$YVvH82kgo$K_&0%w?hsC5GRVZPH}Kg@G3(GPXb`TAk5bEbZ%bn?}McG}ZL zKMQ_!vh=fT9nNRay7ZJHIUW5hyMgnzewH1|Y0}TK^EqpI7Ea1!K@s<~QzbZU}Xk>$P;g$3_A~Mu} z6>VbC$>oc++IfVWRP(ImOaXGp?UFN7f30boiB3q={1Q&To8>#|UlOdMh;xb3mX*io z?-Ez_Rfgc4ZKNx~nBC6J>WkgZj|t1|J2vRC>d9E42xrV$&)jHrfL9XjrU(x`Cg7>< zBR6S|k&-8@iXAeR#DqN|E=&|3r_K3M6t?hI@&e3N;qt)WHtiqg8rRjq4}#B^EmF;A zo2})wyQN|2cGN)>nR0{fQ9;>?#|DQCJB<&E>z0bk#NPve@qN)zpq_UaRHQmQcSXi@# zAm?AHW0<;$v8U;ESrI=$URLD$%*1kO_oyak*jZ2x+##NexTPxjG-FAyOVz;(D{_q` z>#7bO69H}I6k`1)zKgc;CB>9mUrm{g|S3#!sJWP1$?YIM4DWjU*H$haqUx!+=5z;Uq**X&6^_{B_O+N^?6Tmb5K2 zkCqy$x9Go&p@QE&MYe%n1=`1`1s?9-R&LjEV!;?FP&R7n`yHG&QC0DPwcmL|o~^BN ztsH+JP_6ElW&gWoL{MD?%TybV)e8A+8=v8mMS&23h%gu7qJA$W`8TQ zB}=IWO&MH(16a6U5467lo9A-}pHH+WYsF=q5t{DTWo`&Hpx!Ii8Jso+CpLJjn>;~6 z)_S`B6n%n9+?Q^l11WT%Ds<;RDRdx(4pfB>bb$|4z|pcP!!_>&(;CQSk1B{|3-5ib z^lb_mFkj#TGGDqO@DIXM^CjWzyOEK$&-U9LP>beo(0;J6iMj=QK3LdD$TlFh*k|vP zw3%z`2tdBFcCV&%Yha-p%ON)264N5!1TOG(o(oA8Nj#R1U3Oo2>49=%kP|4z@T&sx z`-`%qU3_B?EfD$Xa{H%+A-h&85b9YgmcI2d+84@{1hf zk^0Ina*an;he{7kxr*h|G$2}WA-<_Un=%ktfh!AMiS$E{tQJfHx^RBAQU-EhUwd+) z8NIixs^Oq{Tf;fQhYGpXEhuhy((7oSY_JLQ%KulRL;GY70+H(Zu z%{YNwjQ-`)5oK`a78xJGR2K;s7oBC%X3peZ?p`3Zs;9nytdtibMp_cxE>ByXVK)%k z?Vr5Qdja`ViP1^5NzW{! zW(zwpmV67MW@jtB$#0J;jF#u+Ftw`cd4uM38S`cVT38EZsf>>W7=2=OjE96vCB(!J zG|58Ixx`ACx(_uLml;*te12bpxHwlg3?d8Yl)w3PLbS^QCN&vRVS+^)P0K{`DO^XG zM+k}?#c6f~7&>ci*VGcWhP#B8>ybBDz8_fg2z}PHN{YV*}vsb1g2glj^-y{jw@vk!cxKHWBYF z6Va18v){dD#d6cWWLX|n^}A^EEysL|W0V#1S0xe^zvXo(`bWj)p4qUCeBd5u$3*s3 z=rOLNpaz`%M3t!5b?c1cFw#f&PWZ0)bMX%Ze@@>Ft~IKA(l4Iaow=0xc&FV3CC zH}M2yqPzcS`H26)2o<{4sMVVNhMJ}kK*`!YEqLZ%8on`S?wXb>7#2Z?uUOGrr^y;- z{RW!K#bFmDskkRhn)?c_NLp6K%jrvI>A@*O5yQOyfgX+f*7zbN)>}>gF&2L+tg}l< zFlyeH2@vhkD6X*V_Oo2zVkf=&RDeWQf}-WQn{e}M&N+BYMh^(pl{E^4{e*l_d0)E( zCH(?e@dn=d7yI_95`7h27$C{wti9HktZC$YOz}o=9^3xpDXEc&^p&l1=31HOUgn^^ zqEa6R&)$$I5bqv zQZUR^hD|k36f{K>^pNut8Oi9PCg}rmbeAZ&3K`}VYEbkm=hRV<-l*=U5-G{)KqB2P zlTm7BN5}GnO`PD6XpcPu_T}vHMLLcbsnq6fL+sF=Jo;%!tb7lxMcNZ$aI4caQbDLx zr4MAH>X~3ir>T^ote19rCaKEAY9yz8-cqbCr-u~DT4on@hry_cutekc@n?vA!=60v zo9pGxP5fzc@D$<>tMRlXGQra<0r*iSu~xt3U`_&wwiF9=;!_zHixYMj>%1 z^awh}oRsUt3>((^LiPepTrPmT$O>Rc1fsXRu+Sg9p+h^rvE;tOBImX}f_!jL1?ohd zeO*P4-MhK{N@fqu8M6RqK7QlTI{2K%qxI11_RkTDYz{o-&OZ`c@bdA6IOA73vg%DT^0zAX=dliT3F=s?~AeW3gDeVn{E7(X(v~&rtr#NU!~X=qM_Q9WMMe< zI#Eb37{sG(aw+(7`4*~y=2pBt&G)(A!B;%&55iY`rw}`>g_SQS(rt)Q>3emwVONw@FPN*d9%Ay2Rw!^ zFy?$sSyZgYPzFmRQzYB3S7hj`nL07OuWl#tX!aGBBx24~X;HM~cSESs0I42n?WCae z`Ev*K*KTL|aMc?x-lcBUoo7_k+Z%o?OG$T(0V?z7bLq3Kl8f*i!pi9zvm#>J?`|ot zuiFK`dcKYFhUoIHcgrhjv%HUZlPvEp$whg0sN9(D?iW~S%z@V;?YvmZy~Hi|RLT{m zY1%67pcB)({*!+ly{=E;s%ybj*HNb~Qm0{Vo#seM>3y%)?iIsS-w(f2uiau5P4D}L zI49677yHGW$0bX&WO5MjhC8%HhlAHTm0QCK{GK}me8O3;qUqqZPUS41Cbjf(qy)M; zn^4w1NAL#%FXOeUT1;Z~&((&7sYAk~4#jR0pI)A!+kc@G)4Tm=Bp!_!FGv6tsfxn$ zWqMaj+cI_WmXPX$UAOBlZcx$mJ`i>HCT9i^lIRf|bgB;ykvqkL@Sdxhk7>?rt=4zB z`%x($i8Nv*6-?=?>)kMU{-S$dn@078HPeF6$88qM^n6|6eyYMdof9)ucr6v)?7YMq zD*TxY)^N#17?!ksu>zy!Qu?PMuvyl@W_JtQ>Ks}sqqJEDB$*g<;v|080r{60S&%z~ zGv95~%)*oc8IZk0sZq4#_Q6b&?sSBt-LH%H)+y%vBvYM69YLMGtvU)N-lD6sw210- zajUauiRz8?>TJ=8>0Q)7;&<($Hmf5$IV8>$j`1;P+Yr?~w+^B{vRg^o*i7{qnyEgQ z9*z14jf*mmkXS}F*T7kJ?>HBdGHOo*sFs|oWKOMHoS~bZ(~0Tb^m`J&Yd3xKOiJsk z=Yp!Qj`33|(dd@G>XWHbXJo3>F-K6Tvv0tB$cvSfVeuq7#Rz!i=DVn7io!IfNNrkS z8W!$;=Wdf1cFC*>8q?gMqOCAZywZ!zUzRM+}9VPkml|E<=45>%{c>JW1krtxp>rEmP8O593U5ZDF*cn=)kbm%(D}VRLaB z&MJ|zG7ZA6L23}b94t<}##f6elFiPIZl)gsQqX%f zb=CMCW|$crd_JmcD}M_~32)F?%;iE#f2ox``UbHVDmP(YI#;0Kht09FP`{s=6{TYH zD`a0HH91FA|F~FyS!wngXod_-`B{Z9`(aI^r-#+fH__39Cj0v9L6T`^dz@-+bMy|Cm|uy7>yWQ1+ud(!R7u>7-*! zN`zmto#S<4J?8VwXsEP6ax2DDX??{Bz3WQ5OUu<&lc*=(DC)%e;wdA)TJ+J9`ax-u z`Cf0NC=n~ke1mBkd_LtA+`csFGT!+6n?s@}^b3`4$5T9~cwx_K!?=j#kJN7BxdBLu z<2NCWj}6qdun+L>LVgsFghk9M#I^X^fGo!@*Yj%(MyJBF;lgSsdNQ*p9;Fc+q>G+~ zplev-BF`hCtRQC1>U^aq6N|hvG}`|bG)B;q+0Pqwo!l}WZG9gbP^93*1{AoVpkFDb z%ngOGmN-Wm6^?ep5pHO@;Y2r_;D%#$IBTwi{4-;-D%FF6GR;k@O(o5m-|{%G<#B1t z7_R%uK#)H=U|K&oKnPE6Dy;9j z`TYPqJNSUWBHZJKjtZmqbNy5An573IYm{=&pxk(8fE})oP^rKBCWQp7ouu@%{;d@f z1eS-t%N1Up3ny2H%o5or;g3g6zE^r%Mfz1)fg9#ac=YlpodjK1rIT6e;X zRnLh7p-18Hz-SLzU4s}?4Wq>&u0_uCt~c&$oqc7F4|B%$v;)q+2MDH9&P6_y>q&T_ zElM{_d70w*Yo(iyV2TUInCb`VCG|-eBd*?MtW@y8V56|EOTpa1Xf_Wn&@a+&bCoh@ z3%*R=Kcg}&*LC1KxzBv{RCRVnKtSO)&3)H*#Zt`S^mNLFAnnJ+N-hJLWzIZUNigZc zr7FfHjPPd*mxzl)> z3GwQ&{xB~(Xt%MXuIfPf)amR$C(-JZ2Vl(F8xIUOD%WMt9d7KaFQ_=7`JPl-u1<^G z${lW`L*=?W|J+>JD`45>jkh9S%^ih=2P(Jy1{X}uv%_CuhPY~QKKg%a^RR7S70Y8g z{!00m)=4g4DdySc<5I=sxJ5r^H63c~f5z0u;bn%R$IhajK|B?Q#8YwQds``7?vkr3 znCNlwm0UGAH%F!1Bq=xR6rD#z0pnNkCowoT9@KBLRo+YG&1L*KG`MU0BK;;Oel8E= z=XK<-{6xU8^=~iB$eVU2d0MA=-~Vr(k2bty9g=X9VKO~a=xx6|ze&6E_|0*DF~Z;S z6TQD=nuM#|pFGR^L`-hj))R-NL-iU`wIr^jDXL@1e}WHAzi?mfYXP zc_~}2LY1-6-(8`IOi-N&$W;JGy_jAH6XSlPYNyW`+l5{it~A|XPob`9 zM$JDu)DM_;iE)FbcH=5~IJMj4JlVg6+C7_RDdS8D=bJtKd0aGS3te;ui_@wz zt-w~CNKV64!fMMrlW*dvi>pz_WruyunsxR?GPEU<-#yH^OdS~1u|;FPZH6L z0mhR4wN6v#ROifO0Q5c}opT*9rRS5ZckxIvkTg0>f4%s=+HB{Q!c=z+;PaO5%1>tH zYFp$Ck>$WU!1pB!#?VXN`ytg{NtDBiN6rvtjO3e29||i>Q1(If&3}Gk=n47p{9C$S#mAcV3^!yn0kFIRT$@~!cNJ>-1$(#Xi2tIL2-AKH?2Q|`vy_IwV7z9vOdmh1C(NaT0y>+(azSUdkuwvt0Z zNZ|P1?EBWi@&3|2b1QNavC=mteKKZ^bwEUXQ}m!ZAE)$pt9F^#%9)2K+w%Z6kBpk%FplCIY*(d>T{tFMKsR7V`-{p)zd)H{OV2n7)p&e;?M-=j@8`9WR34P`V?aphv%&1?l6y4 zaS{G|oai4EChxVJmonpJFU3!oN=(VHtHhg0{I1+1;bwh{5IYV7?cTD)ep*>BorDWe4(1KDPi-P!Gy5RG-97g;@+3LMv}U?|w@Q7-9+ccz)a<{f57V4w z05Y9Dl3X`SJZ)=nmq`z*qV5y-JHIE7ApR!^6M+qJ@x-RiR|q3QIRs7_U%E#8+Xb?i zFmNmIZ-;0J^LaedjHlv+7s@3d5?hgt>EoqmQuSgoyMsQfvus#6;IPMRXwK=q4@nJ? zo))DmBs;3snqILEx2BV*76NVb8OVlpARh?LF01;7m&hXZ^1riMiN(3>2+6=WU z#u?L2@eIUy-KR~w;6CzLfwhcjC;zU*ky9R0a&qje$H$G3mGTcCH(yL08+_$Zu(Tm$ z4A`Tjls2eUfHb&B-Z{BDS#+5pG$;No2ykWc*;J7yx7Dt$=JnE362pe_QQ`4&j zAGF4oMar%iA|9ONG5@$LT>KT?WBgn=c42!W({x%MVw1??V!WKbuA6aV&}Ld}4EuH% za}m0LW%qK6*C=>J$apL@s=kyJ>q~@6_v6dzkXihqXj~xUSMw<^V2Uzo{KcM)^=`Oy zw^5S=q_`Y^MRu_m2FKbN!-CN+9fQR$vCcriGcF_)8E27H7AGKhG>7U~AYdl4X%+;qcRFrJ)2D(eBs zhet6=3R?wXi&3}ASOaQvdfEALFIkB~8VI`aYD9zpaoR=fbm{X;co53{%O0bV$!P_U ztxV0$ubg{C235_?r2as({q$R8MR$Y&<#O)^t8EiCf&8qT*kENjL^$&5J{Gea;Umm{ zTIT$7hvzp8?w%|_Rz1yrl~ESIKt8zi*TF530ZLoHBz>W-IO!nc4klN&ZeJ}ZPkyj zuTQT1Mt`&{8%Rrl!LeP=heUsvRp>1Jo=i^f_$Ahh1*|!5%Ce2N7p)BQ<}UM0Uwz^| z@qCHMu^Oh;%H_^NUK@*Za-%_C{7IfO)XahRacxGzue^fVp&}KK8#;W>^{qzqt+5S4f&e3ze-dJap z%X-N{X#VDUPgnI&eN-~>Jk?d75-i^=T{W#I^Jm`4^oN)g+QRQ?HsepuIlHmd&z)>gT-r)t~8s=7V(4lbs1rkV`YfE?-LoSCNZR?+z2 zQU>SKKMVQPrWvo~`=a{dJ$oDcl= z7^!`s(Z7o^=K+WX)f9vJ|C)NG>Mz`KXb3S)QoJ63#cvt2ZzVT&=vXjvgfi_ecv8U` zCHOyyQ=&t2{T7rAPDT~Ya0U6|2vc%Jb2vD;-3MH@F{>+2;K%M7KWd;*M~%^e*?ud) zD^5Qy#9cyee4)qs^+%0dFK8X9lZTtpKBt6A8;q(y%UcUiruUuVqm)<0TQ}!t&V0t=A0@1Jt9DSpj>KxS zQ-c5wmBuCmJyyMC+S~iqlo=1NiBALqLy&A-oa)BqhBH&At|47_>N1|bp;Kk?r9l2# zKt7!jfn{8F+2vuko8)$%kiEsjhazZK;4$G-u_vr_O!RpQaDLs%a3COoe$21 z9E)E5uztNlzkW(yLv&<0FYp>VAjVX_fXlxeerNJ~hxiFJ@caB$6aNW^w8?}!3BA9k zcz-f!e{|mqKD$P!kq~b0189zyuAdwLONV^u4X&SjrI-j&v)mF+|Ly+7>QJY*q_DE;#66w(-h|Ma^k&(oiYXLyzp?w5-C?K4nE$cJh?9X&N0XUN43 zg6Q7GT`P_WH%=<{z2>*hXg(DhkazWF>6=qfQQYqgW1ynt?R=Q0@oL%R<)0CM)q$F8W+BEqR zs2pZ7oE#zrgo3R+^ljypOs@NV_aJof#-vY+W4@QID_YZsy8Ucy0S*#kKvfJg7H`H$ zn=$(#g^ds78MEfflgMt%O^_j+&|dr`YL7IupNCwFCrMGa3}+Kb-t6e)nIR)Lt4uyB zldGDJN|0-SvA9n^XXM#(DB)v|h!<}v`mx=`oJ-q!n=Nni+j@)YZOr-NEV)a1?|X2y z-b=E*oSI*_+^yEdDG{d_pJc2I5;-yF&kEpn0k}N{uuoe6JIE(f+5*^7-i}rP-xq-I zrvSeH^EPUjE1#Ux7C^it$DED|;0FTmgA~Ar+5(6)QOwzNW*be0T!=Y~+j^_q|IBae z?XmLh&)Ry6$Jv;3LtAeRd3#06+nKlI<|bBwPM?xl7cF`43@~IN&(5-_urEIpvC$7x zt$kW0?huK%9jU}$koaABn%|}zLiNg#l6bs8X4g)1&much<(wzwsEtK0e$RSLqbE67 z7X@%&F-nYA)S;Uy-0SwNGknS^uL|)0SrUq4gCtd`bel1I2MF4~La?;I_$63~<<|7) zJ@Bo~Jp&sQ35mCdnlH%K!W|X+B_ek|nj1xtrLs%LUkQJ7AklRGsK`M_g$);T4)8L0 z{q{5U-F{~pA=iPLi45w~a1S}X9!%#Wi|9lT(!Oh#e+M?Q@*z`1`AMAaJdlg>Fh$7iIY^9#tMmQ&O;JzNOKY`>M>ri*g5r ztIXp4q0*gGP60FJoLg?K!LJg|Grah=cOA*bVqr}=m8YwIN?n^WN1B|@R5XCSsWbi7 z+ujOmU1opc1sUZzC2ZF%1DCr0IZZ~YWZ~tI5O5_QP*Lx-lFKe0NWm^+yMa7Xm$i~N zo!g_?$#P$GJoyEnL8c7%J#g?ayKUb?H{G3w#@1xGW{0fRW8yFHop!;H#y|NMu1{Kj zsr}65&eTAkl_P{COq#U)F4{h#IK*H}_&B^bEiQbjVg)OO{p>8D+UfL$X0}8p;`Hi) z`D8KehiWAdcFm_#a6sbI{&G7i=eUv17_>2Q`;?R2lw09+`a&2# z>Gea9M-GA6i0;_KbG^QXv8zw(2IqV8+k-Di@ne$sL+pi!;}^84aOaB47fxkU^ZeE) zp`_#EUrLMR3JxXp;+LoNWNwshos7$fRSqv8pt2*=s$S+KrQ)WqVV}od%PF$6rROc>2Rx2N`5}2?EB9U}?ms2>J zqQs~oyX$1OI72w)Ftzy{lN(T0EqF2|e*!q zBfo?bC4^nj%c?0LtH_CLmK@Q*Xw$06m9Lcvi5e09G_6HN1Y$12dNzxv=6vztRTsaI zy(S%hp_ndNrHiA_NF_vZeJbs-0&`Wr?{IEYu?OwI+^PeoR@_;2pu17yr|gimC{I4M zS11V4Me6ftWG)v;A?p*TH==UfL7Ci?jo?0jMi!FYsD^!&wM=f{m9*Wyz2-AweFD=z zjj+}gNTcR7DjI)VI1!oZcPrA_{<(pPZUsl{=-qBKsH2nI==XJWvK#HGqZMwnhmJn^4ci`kAc`hRtir&{`iff5w0-67xq?tj_)@IOO4M#7ilUqAXpu4tR99w|f zH8MN7RWIY>gUdUUOS}=>GnU(Gu2gXAvEO;}WM7tda#XKn0x_RaTJI}g+iJ2mpQt5f z-=X#%`Up32z(9LlCa0KQzZVS_ERB_=lF?A++tatTP96KUx?eHgJ<^85N zPFfX5C&OcJoWYRA7P5U|@nMb8kS}x0l^*wA?qFP^|LMUP4G1m)#AbsYj7{e9`Py>) z;e(-t<3)T@`n7&rj@YTk>(ZZ+2X+Y4oRyBj^LsO+! zXo?xF$Q!}Y)MKUIYofkAV}tpIhl2GV^8GvX`V6FDF(9|-0EiY;ua@x|XFJ83wn_|g zgOnPw(F|G8ii{0g<0gb_>fB*)2Sr9M=IlHXVp4acF>4Y~-Eno7d3n3(54xz&pJ*uW zw@5IXG3z#19pJ~~5Y%yv8AMJLYhE+g3i9+YZ=9Eb5BiY-aYmh>o@J#tL7u(x%BlNW z{4F1^X{j5?;vk6tsi z7vCqO{+WjJi*Bl&o!xeRsG^zLDG-wFJVQZ4|6jsrN#jyaJKvmQ#;RB4J+x_OSkjHHrx3UZ<6gCuArg+D`B+cIBz>UWoqYV_o>FG zw{t6rN3op{z%i%UXrrA2w%|*JM-&4^{lDax$py#(61E)nn!9F_J>BNa zakE!-k+V~a{Ti&-h&YU#dF^bgojoAO8kiG`-kht{wVgf=S3P-(azkRU!FiQNF}$M4 zbnsoF^akd;(f{aZfg9bTqeX6XgO2ucqtl6EB+-rCrDFZv*pF4L%#B^6V&y4)Eib(x z@~qn4n>J{fNuk2j$_vs2VUPQtSDO9E=$D`Qo-3^%j?RFLN;n_rE)oeKYqhg{noPgV z1#IjsHUyp_@w;LUVY`)|xg&%90I7^PW|n#OB8qv36?4VWET%w;xg}FEZ%jRkttpaX zzCI>HYbGAeVqjmyoL^@urqfX=CYP~+SnVstbjwuCAEq3QPIHUtFU9<&Q-)6KcQlJB zlVXCIirEzT?yV_5`)coE-Vz4VAQqL%`PrK)%H&tkXNaOU z$s|?LU+btC9uu&0bdZs{P`|D^Z)gOx8l+GX3 z_m7}=LF`%!U88sB&}Koua_X0A?NKpQJymSGisiYnO)4fAv68bv#lZL~wp7J>xUs*e z80cTUdtAj(ayNFmikWUKPsKt?DRme+sl##;jbd#0Q7@CUt^R{!YAUmLf!c;ZZGmhw*p6id z2(uOA0ub}zKf_#nvLWyf%qIrw8Ul4p4qQP8nshESY{iwZ6<$ZS`i8(OtO6*2R*HYE z_NisEO`R>OWYOtQCbi1gqso}(dJ4!Mb00hN&r zkUN)-`LY4+3)EqFaZEk8`D2;@>3rBhUtd}83g~~`odNV8N&K$c-#49W-3^d_iaF~M zX+EqCmml4N)ZX;tOa*P4cywCiVnDS!ZEKgIML#{d1%Xnqf0n7B?4w(d+6cR5D(JDh zj%wGbt?-%b3|-gj=oSQmjXC`@6%@PksJ2LLhp+iEwCKj8TM(N9_N6iv^!Xh}wM8K* z$VWkfJjB!jmz+}WzCf*xE_ALZ%0_Lv0y=Edk~ean639%(?ri2FK)<-T`6tQ8(ak?f zK8Bk|CLaeh2a}JNHD8f@yr3ETF&e^|%`9*IcvACe$;Zyk$GMMgJy&PxdPb1T*y`Me zaH*I9Md9bqypW{ZR8s5ng=^n)g3|o>Iv9{2w_23pNoOppt&^?F?Z$17#eg?R4UYRB2zFE~V{s zHmPWOX<|Taq4S_F?K6Sa)h(@yF732KlonB2!}JAC?5AxMHA5Hm3%97-+AQi!Pf<@v zE{c3oLKK#yD7j{%(-$~5v{lscx~M|8s6$+nu;ZlZFY8tQcg>JqD}r zV@!0g7gU^zj?bR&V$V zQ+knH1J3<_@=mNluS(uR;4Jq59!?V&H1ELW!8>Zy5^J1Wq!z;0M~l)LQ-^z!r6C9c zm8*~3o2c0_gj~Fuhs9*GYTK0+`OC_9gW#K%e>Pup%>7NlGjrbUy~U3t24>o|!yI%P z=ObiUt0W{+@&VEme`0M5)qyund)jD3?tUAr-+|-tir#tLIwVGnK=+C5zDps3Vui8~ zW8S1(yL4=E{yy@@2P*{KhDGl9`| zc*B5pp0@ngxw zq}?T9v}6~WTg!h-Tcw-@6Ec-^lY(0^Iay9f#WR(Y<0)shdaI8Dc|!D!Qm z{BOcZd?wf}0G9a#-CrbqavEiRVS~MU5z0T)&SHHCB-9n3+GAX|Zc?A-l2-Jy$)~Tx ztxv1A`^--3-TbTf8l&snZzi`hiWCj5)LTAX!KnKvPI#BVR3Ih;xbL zak)#MLqsbHOYY(o3&{?3%w7H^@WkE&g4IN&fyNK(KQ(^Pe*2w@jCL9iiebv8p-JgA zVVRAW8vWz{vIeT1i7Tg`D^>ZCTb0XoRl?q?Y#H}uqPbXd`Q2P6>s*7qxfYR2$q%Q@ zR4_lJC6DtW{UlJJ`sylSx8>!_u!y$5MBHAy%9$C;%N4i3dyPD1qH$xOb}z+0MDfB% z5St|{Rj+xhy^(&kCLL{3Ra*CD$I9{V|YWQjdUcsRh5G{Ey*fWEc>F6V^{@NymnU_Z%QwRVE%-d1h? z&!D>%pU-f_e1HXXvUBE0@6r2ST*0M5!J1@(%(uYM($}w#3Gp` z^3&!!J+U#(V?D9q%~yM3{$|MH)OVLObGfOFE9{_Hx5P+~<}*B*x;CSQocx5rI3PKx z=+e)nqAl|9E%HB{*^&D6+p{aFxybOmK!{=>qV2frSSj7n8C4Ilj2?kUffo>`(u|P` z;8>ZvD{6}bF_D}|h~tVogR$A$R33&dHhXi+V^hmxW6NV*%j43P$AvAA3tAp)TOOry z^3Pwn)2E$lK})B60>uKG3V6xEeV%l|iQ2oVkT&0kBP0&SE(YP@fTfoe*dQ%Gn`kgP zTcE;zSh!5SR*MPA4smePdy87%)~H4+yw-vfpdpKR(q@2T6uaEIV<}}xN`p#4#Aw{} z{;IFDD~kEs*{FF~0Y@YvvI(u~rDCb)QM+< z=lP7aG|#y#^{P=`?c`5VIBlMD3NK|y&e|)pGAU2!ch3*`>WT&505=q}$CVe3HA~l2 z4ATO~uz2eB4=e*UR^tB79$0{TmLgOw8Lv91(MATK3DhMFxEG3HH>%I8){H=?6(|wZ z&G$PO^pqEaa)UiPVBe_SZFS;;h4FtxcwG6O36$x4gUsV+Py$E}RX{f1Uly|OGSN1# z=m**A<~O60^^K4mHDaI1szUu&DCyF7u$S2_R4fE;yYk!iioleJnaHY2(*Y6+Aw{pI z^$+FmN2*Bqws~4c*S>3X$eub@EYMdA+G0mP<+plUy^gZcS%++Isu&WE-s;oy%jM{@ zH+}3B>FNy)M#p3;L%P&iW>lU|zl3TJ3tI1lO7})EX7WLEALs+`_YNZ{&BLS02;A02 z-p9~e&pVoe^&jN~Zu?NEe|Cv-goeJQeb{S(X1afD@46u73gLdPZfU=14=FH9KbhLe zY&WDx9_Y-~BTy!Zd^l!}Ht>>j+iEE*fg(;T|82sP3|niHCJDYwY{UT|MGYLQMbUE1 zWyv!Na!~L(KQU%MEJA+KK9G7la)Qwu&@N^GFzAXc)`}a@0lYLX%cvfskVVC%92Iyo zrZHr_==^PutFyQKLcQ>uhe-)S{9|B1>ANEyUfDNGoQ1JtM(Xe!>5L;wScID+P2%;j z6La>`f*>krGRIV`NZWS_;Aaf|dA}XHv{%rAqYy@wu@0Y+_Q*>)){6y~0@1MVi2IC7 z%Pb$R6GdmgbD+qmUJEv14cE$)>{2hQPub-WMD@K*f(*rXqfy;~Y+{+F`bvcv%TMYh zHV>+M6AMRgA!bJw2+A^xKM}nnH296#GU=$oNkz!>l^!&zRuQ-FEwm>WIkVV3#lQ!! zQ6oL6C>R}iQeHrOezaraioKdZBV=N3sAqV=zAW4ueMUi8N{H`~;`ep*Me?z4h^a@B zQm5aH8u4}#|2Kcc=vM;;$Fx>|D#KHf;Wx(W%D@Rb7_e_SslUJegLdWCc0?s7-{w|+KL5+7w}DUPt!JNsA?seN?WNu!&@{L>u=HNwa%nfbf!>1 z3{svcMuwu7Kl7-FP>U}!Y@0b*Cqo|H=z0CeUwdD_cM`9!^t^uC`*pzD=B$z&r+IR` z^RX8|Ew5EocCHlRVb_JGcb8A@Dz(m9vdFHo0a-NXKrd7@Kz+aAETmLfzsE`r)^G#c zm|nxF6jrs^WRi7^^JYv6lP|Q9)omo#GKnXUIP&#gYCVrr>$$Tk6Nu$%O@kJU>K{-v zcKA4U?sodGP~z2x(sm1%slshQr-CpjZ zU8n{bmypv6?TJOsI_#Tr;m4wVw-?Li-CWKG!FFLLk*K%eOA=Eip`L-IekAgZ8blSb zg;yW{3*o+OUnDp_=Hii>-Lf3_^GCjNf#0444qRDjnSe%u0BkmKEheixX<1oNQ_bT7 z5?6nq05~(UN_VRHD?_N)UwNTSU+ft@57BXPSd0GeLq8kQ|KTSv-;Q(BhJB>c`jMtS zn8gRFNmk^c1T^8j(D^YLWQyJPADLnq=>xyjV$9ng%gUusoCjbM2)!vC6{u$T{>DSq zzl6|)B>X)=x&4&v<^dU77BBI?))M#;ge`CPS4jaqz;{6R5${s=KNOAHZgQUXn2aKF z0v!XGtZE7S=c3$U4A*CgMIb_e&2U(!t=wPSyPoFzIh2np2 z_>^gsjg2g2tR^45VmWP(ck~n})AnN^;XtLfCA}w3!d8z-yyux8_V7aFDH-&G-R|#J zb+AK4`>KOoBHfn?kw0cl)xqN`a%UdIWT7w8erb+MYYTtP+$B3z$coBgOHno$>3L0E zS$}QX?eEH^B{q#=(*o-!MsOp%qDfW@rf_60zejkpwro*2hPIyAENfGooe9iExxfs@ z$RRsxPN9UR{e+a{<}qW^3KpdN&5G6Z;8g)nq3EIoKoau$*`*RbyG=z!wBC8~MYU=; zMc9j^DiEqLJwnzqjudX!NLITHHIlD*kUEZ+*U@Kt0lase9hE{5;#LJjpOIK7xSeRn7YU?7a%x3n&5!n+VtI)@ZfTR$FYfKWeQ~ ztAHqwfF!{eh^Ppn@}k0BR|VgA5oQ11Gxy%jZitn(KEMC-d>-=oOm^>aS^`;SW@7cA^2W*4P8(vKfG26+ZMOxh49sV2z?4W|DE(qCxBEq< zoThq8MRS{@orzMe;8$4P(17e9ftN$pX+fea?l7+NsD|6lzL`De(X;Y_^2atxwkeC1 z1;h%;=5&@;RlTc3Fk_RCDexYtm4fl;-m9pMR)EQC91h#9OZv|aNXm?|dB==40 zp5jR-<9e_$pp{zUqY`l}<}dyx)WbU{eDaiTaV>TIWPgXmZT)E^4WOtYGJt#IXN$9c zi~&@e*w$rUKRGLJ@(-vuM~`IWP@+~f3sAik!Mts;Vg{@54`=l&8^QJ$Q;>zK)j)DT z`m20&r!S9t9IM;#+2Fj%d1}C!UCR*l3tpTIBgZEk*7fH7GU)Woj-4`*9P?tYfg80! zoRnV>CToyAv?|#R^pUFHvKWw^KgQ&0}!s zCEgk1{5td%G|%dzW>OJ#E?HILI=ikJOM?Bjx*}&dr}V7+f1V1z%*& z`zhOH8Ws~EaRJHigqncaPZiG|Swb)4!6=dY=XwC zc3Y&Zws^niY~%7Ay=_NM|E7+l=}ey}#VFQi`TvNr}71v zt5W1Bai7xp<>ox0x2-?y5Dq%ztSDRC(ZYf6%TMRu^0e$#n40D1wRTMWd?c`VAGNjl zjL=S8i}#728qbUORB2(D0(cqLpH8yzWVO@Tv_~Lf$JB=L(Wk%?4DIjRq{I|{Bl|0Q zWcH+taC?5HvpUDFcarimaS5Wms4cZ~O>x&Me``mVKnHTGayB zN`Ol$@4`c5bT%+5nS+cqvL29UBTh6z@K?=GDh)Meg+dCn=*9m=kp z$ZPARZ8DCKbm07Zt)EYlr-yQZWM!Vbeq3I+*#4#8jN?U)0+=yShq4N#$D_;TJ9d@C z)@>zofHyL`gc-F8+Q96MJ-+g>{^EDT-C~o4_tp3#^%!Tqt5uETv0`RTM(6~8be5xZ zXet@SnzUW5!C$vc%xtSW8&+vz^+YNJOhBxjAL0?@Mt_L zBhYTzt7K`VX%o~zgC}5&m8RLE2~ATYS7!blRuxS{T53kpTq!9!T6ee0&8Xzo)m7(F zDS@b5ClNY@J7B$xIh6=+jdx9@C-F`5zPc3Mq%4D3V;C4N)%8T_m`G{Xs6=UrY!z`z zT}zpXJ$|i@Q)-zLksH_o+J%v`?EC!TUD^(=W8$zsh5j89)1%J_=++PrLNkO*$=%SW zxy9;Q9ZL$kRC}QTBwH=rE>(vXhusM+Mj8}-R$tGKFSNb&8#%I4_y2~w!6GVO5b{r9 z3D!eW09M?|Dz$1^dBj%n&+&bF;?2w?sAVcgsC~&Va^?rfaT?Q`e5sHLu>QkOjw zDPVt(NV49La{AR!N72`3qY5p0OVlL-f$kIUL@r+ z;RmBf$>JlLop-DZn6|n(V~m`hbPO~;VG5R>~^~O;Vq9|3nfJwa^Wb&@MNv5Ar+oj zm$dp1hwm?24u|hv(wfomGEZ)OE#SNIhSZFImd_4@@BV)c-_3kfqyD77B14|R)?Th~ zDObJ~BSZe$;`x5(*%)#cv6k(L47oM=eo`y%nP!^Q`1y)e?CD%pK9=FE6-0#_Y6X{9 zXay*p6pUY>72LE)D+t$W1>%<_I)>K)LoGXWBdQAvFzzo=Kgxuc0o~;*dq5k${gEMi zL?#ZMuSXR_L}+65jO=XqEPbhnaQz89ZR5M02jsgV4!yPojE&YSfqXp!A( ziY3}k7l3Ar1C8`dR87pfbc@|to6-(#a&YhMoa zIK(LJyHvp_?HgK`*1l^1G1hESu#4xc#w`3) z4CLYG<>b^8E|at_*GYZG6%6_IQTimk0(%~PXYCYL393Yhf z|3gxwy~pJ+daH&cX@gwWQJ;N`D)#mC`OX2WV#vn>1s^@QSB8j`7vy7vAbZZRd`?lT zd0U@dE1Bb)Tkr$hUe7Bx1A&$YRe>dC=8(Ik#1ym~vOaxX_Gj~aWKBWKA!{?~0i!>M zuOe+JFJRV7q+!ZmW(*mj>Mm1tKPl{}Mg6KjqLbgNyyjrPer z2zK`4L7+bSOa5oZ-%8p4Ovm>d@?P+>mWY&fHlH`+r>|4k-&A1>Rbegg152B!z8x}^ zA7;lE@FQA1g*Gr+d8Na&O%NqL{r6Pb;Zwc=qpYCrMZbP6{sD|;@ zZ(f7ICi0OZ*1PhzRy7w8g66C|r35ga2lD0ziVud_tw_YLlAAx8rT&E*AcN?*+P$T< znc*|z17v?@+3m&S^3y_FIc=QycRI?g2pU$EF#ntdytnx z9tOvTO1d6Gf(w^gf+Y#>F#0Nw)ypCzef(HYktSGC<0Lki;!@=?7ULZHMn?_O&EHC^ z*==7SKLwjPGo5-q{_oj9xZ!n2+f~TwBJjw9^?|J^W{AZ!tP$c3v+kWlK6JtwSmY=GRg%=a3i0zDV56dA}JJuF# zyEuU8dzS*q6u4^>=4~Fk`rNA&>?{|3( z9^9v~-#HxAjm$@YTF9AEg_y@xJx#0Fw?HN6riDRccN#vudwp=b4&2a^8E=pL6Ev^S zS((QN)&yF{CJP!f3(=UNqsQfx88_s5@676h40t~Gi#?m&{V;ivOFwb8gRh(ywZ)Bk z=BwQAkkyC0+zk1Cy#0h4^^IHlQP^O8@dI_enq&!|6E9??97pe@UOo4W_m!15W9&D4qLQ#8ao#E` zl3MF_-l(4G_eWV(?6b~$g|1Jrr&s!3l^-b%W_-yGmMl0Lszdw-I}Vi~@H&eZB@i2z zzdpg;)WL+1vk2b;$*kxnhlrerKemw0T}d&r+pksqoQ*^Psd3C!>#dFea;LAxsB4NX z%tS5bo`B{Xzd&2OSq%K<>yd+PZ~tX5I`CvRti<6_WYwY2Pgk5KwQz25r8JAR!6n)f z&g@PbkdYDg3|Lz+z0J9y>}R2v3^sJSqFf<&*A%S6&2-zD%j2hVDM;uP{3?cX{gD^i z4%oX)L}uA+SW0p1NX5&A+51o@I9Z&(^Osy7R1D3-5GS!tPrMZVoRSK5a+Po*#FZkr zWSilnWsFH2jO%h~~y)L5lF%l45BuTBnn%>Qu^b0Irc&RDGXEy{ImI5^xl zg3nUwKkSplf7JVyGY9$_`Cbs+sDQT)fkhz#lz7xJPN?gh8P5pM?cadx z{ugb`N!L4&$y!xGw%aC{*Bxr#ZRNO-Jx=as%yo02qh~$zY&!8h*9wG{;NwVf_xV~I z!Xg_&P$w(~07E}gXah-#?51!NgN*FKGN2qmq2782vWHo4lvmY55N-P45c}JNkB`YC zGa6;VwS<}}2a>O`Nq&JfgL>60%*a006}DV4E_CKz#n3X*1TvS)BHy~0{I*vyVTmd8 zSelgeCh$0)hS$W`I{?5Gg`t|9a1$x>N3`>YFRJObCS|%6L|etItKiDjuaQl} z(6giu81?ABS)cyNA>75P44aL~4($%>$9xuV3oDap+EjZUZM`{0^>ivX)tpgQu+6Bx zok!igGG8BbW&hAk>}e}b$UK$L_j*)y6J{#NtLP2OIai5CH%jw(L?gmiLA*b|NKB>3 zYqgzo|mpX71_qvUOu7DcrF7TRa0u!gOPQBKrnyk%Qe6URvqTiS8 zab}ad`#F&FfO+K@ij<2pKIHbXpxh4kse&$PwBb#> z=YW|^Qv2g}`;JX--`FFE&;EXc47%p_O+51NzqNg{og$s~P5KYp=jKpbebGa)vV zCmxo5m2C>!|JjelHxNX5x08?A5*DM>9UYSYO!$#aitweB^-D8<`BD1E3#h>+=+;&U-P@*B}t zhb1VN3aDnwp))d@tPcy7kbxmkql^QYZ2GWM z!4b~q%pbaWl1#j)BhFw>`o#JgB7qXDIE44PD84R3-btX5fkqN4oW91;0Q8j6H((<~ z=+&!S_N>?IcC{-jzOE=YloLc#4~34MafbAdbK`B;uwdWCn`mN|T*Sbk;QcK~7&@?y zTP`H4=nedt*Otfvq3Z|)vDV~gly&4(9An4k5oRaFo~EGY^|R0q`*r#Fwa>%gtJV8= zn1@W>npfn@n!WC;c5L-bJyx&V-fnM}CzEq5F1QivA{h|6S>~}f1Dnq$)B4L6snF7c zQLjThbThxQXwHcFNwZn(RSt{AYYSUVH}=W1x#L&QlUZCHh587iJw+JpDL9r& zSXZw_i_DnmFxm~|YlYEXh)GU-WJ-CDeQS9y|3S8U-16ppdwH(^#AhzUhfBu2Oz;R;vKRzfMfB&@$9=BYl=P0%eI7cV9@ z$E9O*Gmy)ERu{T6x5$q+m#{xu-dAR>sdJ*@uxggFi-$#SwXRzxrN8!G#eoZ_W$V3HhmTkL zXD^1|(;KjZ6|5w}9pl3T1?z+?hGOGVjH)xhCfyZE>eIX$1x-u&17Ymma|M zG`w}d7RNH6S!QKJO!nw@`J>&6_2^|eD15#dELiW4Bod(w6$kReulS>vWRk1+(xxwEXG$;&O0j9!*!0i!O6N!JTY=*+$CGQ%E>_ux)( zlt+i##(Fl-k~<*wSti+7YBw?Ta`>%9rL@tHwk0QL&SoaWA!_GCr96lOy67ql2)Zb< z_1ikdi|u^}+IB9~ke$$NY(WqZdJ950N$>p{$7!;Wx<35gfW2H}SzJH)#Mp&MS!(09 ztRR-TPNiy*Gez`rdV*E#vc>S&<0XxGYRgFnQ}ySZ`6W$XDoOvNm<)8Y2WGk+f5996 zx^LQXDFzpcF(1S(VEaY3rZG=Yf6U#}a{L7w?OV^==_{yD{(zHvgJ(8PyV75GC_DA) z^A3D2SDu&JSDxpCDUtZ1g@uDs6X66Ghzz?eKSUE=)~qC}I>Oc+bDsC_6V_V#uw5K6w`#fphs+c5W{E%p7?l zBFj^Mi4{pqwo$M>Y;ss@Fq6BQP*^m3`%S+mP`n+hC@4{nURoO{*kNp}`=p(0Ywv6u z3hVpcRpXg!f11#OYvLCo@iOd62{UT-2Cqmu%vRn;=eJhKLMd_YLVg$0zD2YT zeO|BPWBe|%^HU}m6uLYW9*N|+8^6L z5j(MRqDqX&kCsy@`-ocCi2Udcm9kIglqM-oX*u%Jh^Qa4GDDusdps)f*Y;0DB{oz_ z7Uz3;;Y8)7Q&g^eW!{q~KgO@^pNO4UnXeKf@&jP~<)>>ze)Qc)9!GYolC7RIF> zbC|~{%@I>q^CHegOWCt8EwM3HTIBrpcYgDo-#q6xSN+=Zx+~wyR^1FGm5J7CkrfnZ z%o({dA&b^)o+Hc`O1k~2Js#8KDdY>?Ypi3l2{kf%`wwa7;{HIK$EI!(r+bU&7OTe` zIZGGmjU`TE)L4;3kQv}H?A6D>^eL{H8V<1E!wa|-H@*~^99x$l~~@*uXV?SYS0>dmyXn*LL- zKB^Ro-fDgEq;%ln{tZ|k;vncC8X~)L>UQ<0+jeq?#tuy#_H@|K_iea$m7h%QzEwcfmk&Hf zecdM~m3TJNvG>)MR2)M5@YHG5_$jB4FJZD`c~@K~+pkxEibnsSD}cL_FM$~c3BM+W z1meJ)=ZW1-f@}f}Ir}MW_NKof*5ap+ZMtgVZk9NFI1XEb{2tXywoo93;H^-?7tN69~mU#H>?|Dcv*?OO5_NYyvl zwEzQ4_W_6V2z zJjTi(!@M|)wpMI!t7OjCwt20FLmw>?YG z{L}{?i{JEX!yC=3Iyu-iaPG&AN115fY~@Xp6qCbQ0c*^**k||x0>c(bS8vK&w00R~#3V7Z`H`H9TssKZjIa0d9V8gP#k~lwc-)^H0v1Hc8lo7Wa zMqA-dX8F>T622BVqy%|SHgY4insQy=Ubh_KDjdD2Vz~CTZN&F_R_s+0Ybejjr}>2Hw_$u0&9jciEpM?Qc1*I+~FqxOIrk38q-|3^DzZp0~`V? zn-2nO{QLwc0ou4v8mg+oD}tJ-1d`?t`>OZJvUve-&xm)SEp1c*S;W8(rd|&Ia7!4v zHi$kM+jg9FA#i`D&Ed=m1Kkhh3$3a2uDcx)Xa&@C4t)=xT;7vX=HWq?S zp=Z^z_S{YWEm!k@{YCohdvsr3>2tAdC!k_3-ESH%6Z23V!l!BwsC%8A7 z`dd-eugWCo&>a$NrHk51h$-#D4O(0=IP^YE^iti1f@@coKhNA8DQDJXnW z>^G#R*4I2XZQOm%=5b?keh|i@D99ln@EM)rY5pFiv;ArqxZr~CgOmTN{2T}|^=Usi zOPkB3^Xx~|K8wCvhW2N8d3RbK>qIKZ0#O0241Xiq;&sYrLID(1-Y8#AeKEER?5R}s zyex*micr;goBWK+s$}mrvCSW;1&9W=uwe?T1r?CH4B5vdmraqnqDZVgWA~G&hLNZq zq|b}7j{!;AAB=_(Z=_Tqcc)OfCygC;MShC)aKx=uy+@m5z)OANG=}K`~6Rqsb<=LDj+piU+ru%q%-z=#2buvs~8OC16De^L;% z;G#(a(yAU&@7vp43EqXjAmzN$We6e(2=3VP_vmP$+GerCb7;InR%gV=Rne%+YeQEx zXT*Pon9sIJW(%P)phiDVy?DXV&z_O>J^qiUZChS@{-Kb?tZ%N|u*LMPc>7BJ!=zrY|D zLv&FaMe&~xSc^9cUW^rGSvZ;B=+p=HBnv>{qCHeLfG<(Z3kz!DURizO{UpCCNsQ*5 zy4=jir5E-rgqDFvJ|Q+WejunGlo?2$K9a@o>WAqiB*Ebor^pQBBqT7gNNwBrvHZq6 z^ABfG?NnMDP@L)nua)%%ELZs(IV5+11fw_0=$A9fSkpdZ8jbD$%Pm zmnw#vlt&>56f`MP6?eJagLs6K?i=4I=ecEl;GOJVvvNI^ZOA@*KuF>^C1ge#Jm}@V z9q-c24+G|S;PKTeNLWSi+=!>8K5!G6D))zf)q=`f@c)+doUi1^FOeU`-CMyX5$y2& zCbFG2S9H`w_ZdaX6kfPS7;?&d-&0o zOC{zp!rmTIQN`XMbMPHo1sUrUq(?`?u%^JU)-Y*r6>oYvt~8NQgk@d9WM`9+%X04I zyTd76jfJFeI6>tRc?+5lfr=5ho0(OJ#w}-<)Q=vJZe9%Ax)`ognxlNX2BMsI0$6kF z6#W85@_V6Rt?CXa#A%*v7*ZGo(KgD9a;I0ZrB!p&TSlx=;o5Gg(@J=h6bV1tm>9vLUeuS3(*zjS~M-|S2@ zA`1!hhp}3%>Uin^=QLIh#*DVHY!_Dh3lZGGu8mrjJMPFra%W(*chOO`LSQt?7d1FK z!wswL;U#N)y70E=Z)-)1dXrZtcbHSn?lF4F~WAqYK9-3 zc>}C~0TjopWrGLjp5hAEb}yTvCbPN*udN9Q&qBI82%-bnk9BnG<8#l^_1)3+om#)i z6m@ic*E~MqR`72nAst=cDdv83eRp(y7dX1UL-+9L`i_OTyCgljzB{_UJG#C*y1qNQ zzWaaG`YxBlJ9ZJVU_jq?yvJYgg{)rd5^Y&jI+|WXwCXOhl6Ez{dg1pj$6vs%?Pn+( zx7Am&bd*00>-}jzMa!#As+JeVn9Krb6L3-|+Flpi+FlpjBa3WtdN6`@J6kH22Yn&k z%)V+7k7#?zGT$uhD^F(kPoA{%oXl6r!i;qnMF)RL@fp@z@-3yw202=JBclU9_nR3P z8CeHMMsGjpLBC}dSIeLawgZheYpg1TeX;V4pfQuRG|o)XD#w}EzV!XW*iuAg53yBa{eay!IXeP)9R0~$XLTm&F#n9H=cWI*@ckly+4z35n~lr)zf$;)Kaq;> zXXMH3K1+UR*5iED8oqzwly+qJ-lIx65`4Fla=#VdtD;GK3!Vkv50Qk{ee<8%zrdcZ z@*{eL-r1{1pOGUu>s7u$)AIP|gjB_d;6m$lFko`t)h>RXB(zrwpOm*mc^BxLu?HoJ zwL>y0NV=~s8t^BEWRzssR^pm(GitMyFGwYhDVv-!WF-AV+4sTV?^;fy&FuUCE_el} z+`1qJ5%+WT8>i#@7K|pVd=2bgy2((_WPaX9LS66PsP)3S_y|19ofpv8GUu zXeIrP{mMI|0tgn0T2;N;zO3e!dtB4+6%FcY*-?!R=O6b1u|>ss(JuDD5>^kpbMfOX z<@zJ*asIemwa9o#zE_guCt}d_FgcU(RY~W1BVS{>`y`Osh!BuK!~%XuQ;683ihsc$ z`5Hvr-ht{PU!&Nn5TQl>PA+^62{z@;qu8R9i{kM8&;k$?iaaQIJtUnU6R#T%MjiY*IQ45~g4c!E3t11L z6e4*283=Qrjft8Aeo5Hz!_o75Y|U-8-ZIt50C@_i$V;&9hg3_9M+GPwd2*0@9R;R0 zXU+gH==7s>#D^K*uurOB+^ZU38+l8?)&%NlwPm{OQ4cE3nNs|nh&c=^dZg5oE7~K9 zl%zS=1Hxf-e~1~Y>Vtmm<>sTZpYM^Yj~vt~tKkX(aW&Z(aD6x$bzFDi%>m^dxu*!1 zz6{3{Ej5PveSD;bx;@aVmaz${EvZ#Q9lSHQ#bjU9XjIDDHWh~qs5{i24v{MHquoAH zpjH18Jo=+2x5w3mZT5>Xj_f68WK@DRM;}QUwZwA&I%u0K-2w^b#$c~l3a)& zOaw3>L_v!>Cq@4I(CSXV!%of;G8)Sp4AI~NBXXu(^qq)njX~_ie;El;N1IW{Wo+2&08K4rF%K51p92@i-5X~EUAs7LAH zeJ5A{6*<$m`jpcD2>+=Q6!$CH99brXH@?C-`L|38mwp>&las>W?*!(sQKTk?SO>8| zZJrk{0Sc$jwW^QzH}}Z8DthE?5@g(+1U#|z@;18MD>UiIDCg6mvlUahH ztwpZe2V?ySscJ&0fR~~@HybHpc24MKz!H9G1tLNoN-(Gvy2oiGLuh(V_{JFX1`MqG zu(`s=)O^08gprLxr>tdS);=nh`SMwQWA9LzTp=wDbUA$W1w6#O{BvrPzFccyn{KAn z6*`gkic_zcLy^wQND1@uan2hWE6`acUmrdVD@!b|@bl#CQZ1U7u{Hd&rqM)~5x12J zqRVTftbH^vPSy64G&jyAQufmpp0s0PqF)=^Ab8;Fcc%-^jlCeL*k;X zsKLpqRsBvfIsLRyy%40M$;;f55n~}}7a`zehA&gMn>rL09v&YXzbbxZ{E9e>3M#kr z^p9VZ@^or^aQysup?yiclcP&Kzd1*U%hhbIab|Pv;hgx1al2v#O|$fpE!d$)*HlRB z9ew50^<$B^_D6$3H%558#w$vDVxQOS)~7TYIve?DE(_)if4{j4`5CYl9fgH@K@%#* zjjX>HiNW59oF*^y4$A6dUm01HA`SVYa~JR#i!(%~=80IAR=oy9LDif&x6m(9 zcQK3J0%lf=@W(^TQ=59@EjfQ+ z&>d`4NA!l0r)6fyiFagu6mGP3bpbA=yL7kR+`FZ@*eRpSypHP|7BTdwBG#3EE%Z;& z&_el^(X+FR>FdysL|~E?yp!-T3D~y z=@Qtlh>7Y?l?r>)G&Dk1p@NEG8A~GGp0D1HYwfc3<32eU8bkQ*!t(@hyU&q<)6`-uXzYz6wOda~ycFx3>WgA~T;LcZ)<0Bv3sO z*gK+|kE$$VvrakZgopDd`xmMBu}J$i{QM8^9Q+KZb?#${{j-fqgUdW8U{1hBBv|}`R@K#J zTTPDl%9Q@`n-^k1Lq+Sg$O6G7x@!S8V$4aHXzbCd#ONYm;1$JPpP6IGd~hnRL~>+5 zGg~iyYchAXvLRW#C%ip=ZJ?l52vr=gjn)%f#L&TT6Oou&KBq)K~mb;9Me5U`Y+!>+Cnt7#uV*XUYi%8gX@5 zO0$rnaHFDCpDHEwQu1o}W$O{Vq{()QdevTDW!a1hzM8(FraCxDzc#(Gq7odTQLhxV zqL(rrSM6Pu6XtTuB3>z03?4EkG0JL{Nz?jxev9v$KM3P3wmIjcN&B4^^sr7j0{arr!~yeq_R-!8U6C?A z(Lay7SRy&Zlz-lO^ZfO&)HR;d^N zY=KGXFSXw&{;nPUT-_%chA%1gTz2=qww}Xjpy3k@OsfW0K3=m+Y4moPsZo{gcZG*t zv4R)j0b(OPUoSqS%^A@SU$|M-SwgJyfpr0s4w`2(7un;@C|`&Whl-%gUqb^jU5O6| zC*f=o+<6vz8D}0q{Tw~T-7=NqEc9~@1wMnmG#B2k`<&TO*!+{$#It@u*f^cQl;?<4#=K00^7`~TLD62^Y;6@sR) zSF52aN484yq%6}ye1^K4pC|4e(gt$usTP$L6{<(FWr%@J6))-r(;8 zyd+SuzX3`5coKMXn&B-=hj)a)yY#)*(Ye4*PebQjr2W_ERPZ`LNiR#^{cUPS27OwP z>^m_W>`856hSp(|R&|nEMjv8VYz!nJbQ0>e48cUm56mZSqa~m05a@gh{oJgI(TG2@ zfg-O`MXFpjnKY6sjeMy7X*(^gtqW9Xsr36Fla5a&D3^4e6B2Xi=jm`7{VX|_R^+#U zF90eS-&Rfw7$xoN?f+hX^nN#;vXel?N&s14 z1$9e7M+RW_}+c8LmN>|d=< zT@OZaM%QAAcpFLXl$E+}F*fTRSRlMjA+i}B;(h=;)-*D8H0s5$`=+VK>zOaHf}GYR zCN43Z^_Ed*hB5PQ1yJS8Att9=9BI1pGQJ*_EyQl<1Jh2iY^xQU`1i}4nU%{>!`McD zW^GU@0#xW?1rU}P+tUDIgLGa7V0@9Sqc)J0$@its7)G6;7jZu%E-hzF33ReU+6wuW#?1*MHsCU;IKq z8@5etf^V_$1A^te{$=;P{?1=YC(C*L1A8U5)fzL2Uo-n}HLUWI=k;NEGP@6zAM)JD zSIy_gFAM*u+m&VYa|&~gkNu?i<%a0Yw>h=<7-MaWYf->)DeqM$XBBsC(^@b5R6q4ulW z`naGE33gP#C>bN-s4f4sv>%tu9YCXGj_SH@s4xo0WMfH%ua%6E-*kCa?9FG7$p}qi z3q%i?vq$OR!OtSXz}8GT`ejjR-~17XUayNM#hf^PSHN-;Q1cur49HcYGd%ck&6NXf z{6*E$|LRgb z&Hq{dJa*H+(?1)@{lC*c_yM{9Q5oRv!B>%BiQoRtZ@%-J=lte6zd6pY$j&YCtM2Qe z)C*Oq=>5zP&uKW(#YWSp{QO! zD^;MW_u0;y8EkuAQyzQZMw;)s~Sn9^e>x|LQqy{s4 z&$?GzGCZrV93!iP^>B!Dwy|6jJnIHOdtj_c8v|U>d<_fnrU4u+?p8v&GA0ce>sUl9 zSIpb3de!y=x@>^3jQ0hMDew*q*NiE7aYlGrAGfG8beGtYv7$|z0up0$t+#%G=1);1 zaeR1!HIN$gagB0U#&7j~2RNYjVX$rN39Ztj1KaouULU{~@f*$H$;22mwp(W@$YN}X zH~AR}Y+>CaAchBUtZLn=DzHL!&&4}fzmQs#pcYlEvc?3}K-dDFRi%sP5)S;VMp4fa+y;%KwjWTfs zI{a&ibc)}Qdc((n4br5Osm0EPXQWQj09J-L8mtWsz0*^1;B2mVj!s8UW@cl>^vsM< zf$Ve-*kFA?=cqCz_nn@qwfJ*d%FcR1f?sM)C#TiTZ`n_;m1!^jPV@ee8n4mpXV7PW zUzW$tq8KFZH2K23q?6Ium*O)b2fnelgVWxR9>wll8M(ZX1Ba&k1phpa$Cqf~PdTs| zP+L6mqwpE=t|E^hieUs@pC?J(%vpQN*!Zlv52SIkQWb|wuu>WwNfb;OSv>M@wPw8`$h(pH|HRcLpj>qolefV@3Lk;lYo|?7C1l(Dff6d$^CGh{ zajf#!B$e{XP3n_hsZSge98&svCLP zjWoECRc>Ut8(Hc`{^>@Za3gksI;35w9B=;zo+yNTC}U;6~1NBL!}xuN&#@Mow`f zC%BRBZltRl>Fh>2xRGo(lIcdi`MJ|g2i(Y)Zsb!p@((xj-`vPTH!{cBgwAp>870MMviwQ$GVX&Zlt3dY3D}TxRFCH%l*oY>~$lbxsgxY$cJv^ z12?kGjcjov8{NpOZscV*(%?o`xsl~=WT_i@+Knu7Bage0g>K{#H?qKuJm^Mb22&Hq zeQu=2jojr%=DCq7H&Wq7Zg(TeIWFX;OmZX1$!?;Xa-E87Lx*oOVJBfXVLu_0&F+qb z;|M1c`V!6|oKGkr_z6EETt&E!a1&uFVK!kN;U2>82!AF#PI!i}g3v%{B5Wq?AjAn@ z5WXR_L&;AgFqtreP(_Fmena>pK@{x%L0CpuO?a8` z2H{=8F2Wwd0YV#ec#a`-Bb-7wop3H;2kn<2D!8K9coAVV;cCKo!p($fgbD&$dWm}p zzb8CG_&Z@SVI{#W=im8l4MWT|F8A=Heiks#HaT`LKbL}`{(2`x?Tji(Mg;yTtFx#pu3;A zoNx`{dcq{atpskeNtlHD2oDkdLU@v}lpv;bFA`oOyiNFk@CgB9P;^*q{WSZQK~a|B z2zmluMCM)55$z|g3>uL~qG<2fnl;7IE1) zEEMM-r1*@}9hHLfPGr-|7@p-kzJCIA%QGrxO5@-Wp1t#icSI#1izEJA>@3Qn*Lkwa zipRgsHF<^9sq_zQ`&dcUB6%cofzN}=j2n&60^cI1w0cV8f)eLxsd{?adHSk1fQqh! zcrrng7j0f~yd4V^9N?N%tZwS{f^}2Rz*1tVb&b}XC0P7T_)KNAnu^~kT|~nnkhx#1 zQv9gB6n|qI;V3nWy)|es9SK&JT{h1Y(+Q8+DFwNznSu5l@T5se!2 zA;BDaFM;SxR9X*N`*V3TgG+}QS-n9#g^l76eyhCthNejyxa{r>k3WIdytKs%6zsmg7e~KjwR-L``HZsSaeb!tQsml9&*HBYyBJQ=--r+7 zyn0G!@uH0$5gKQ;;kuwXR8(uKAEXiSUMRd$zMD2r((o<)uALB`u~H|+x@NdEhbsr) z&kJ_=C}>=t$4$p2yg|pu){f&>si5(j@1)CMbajCI#4`1D^U)~?OfJRaaDpgWu9~a+P?Wzm9IyGi?G`Yb;I@x{m1Z9bcyJ|gcUkQ2HDIC z^ypA&M(bP+CmCR6=J*HEIWuO?vmWrNOiM-CPEu;EYEsnTn6ryT82Dj)p#8jE8SI#I zY`U9c)17_)(lOdxaet21#`F|ZVyM5uI<`!-X+>+;1_XuG0{pxrpF<}pD7vOVaV&<> zZQ}3@`9|Oh3#D3%OYM??@I4BZq(7^qbp0rFR=G?}oGMvW;5Qf@&(&GwIbsrat!qDk zKC4Yz_-?n>!2!f7Of*=#JM_~imH^dj!(YZv6a5$Ik>Xr2U0kj7VN9;!^^$~3_$zb5 zsL-tr6mJVJR9Y*^tMk(DpYozV82Km%&RFlW`s-1jrL@V!-I!5tU9d7t7Zv8XjtYoC_}~P)lsO0K^~y7!Fv8lRXb3n)c!wqVme6o zl6a&beFrpC;Rp@Z!^0FfU-kl=P7XL{w+2pbYv8o~KEOduLlhOKqIc9_onpf?r5Jda z=M;J#dZHEd%(D~I(KCg_{~A5aD8RFri_L(i(yQ=u&LzNuHj9F%b8GPI|9dMG^d5;v z3eRa*=Xfe#s-O;om@?37aLnAeA3ck~(ZOpT9}RWQif6BY`7*gQSUXC@$i))9xn{b( zNotkB+ZNMJOV4~4vkFTz>-&%!%fiC3_O-2=dQ3MiL?36SWi{{y<3e=Lw_1M5#qS9D zRacQ+!%-x~OU6GqFMoHT@CH{OQR(ZZ>r1|Da|Ka$g)tudxp)alSZ#5XFTW?<$iWV$EHDL{Y{{Hp%oL)KMYk+J$a1{m7yM6fohlw)h=rddBmP*HNWa^B{;|v+!z6p2Slk}JeU?lhR%x0tJ$=Lrp7BMrcM6r&a8 z6+zNId`$Jpcl6^n8`(}jF6JBBAhg^J?!$ILt#s}cLlhb(mfL6?rXE{C<3V0HojZ%D zbnflyH(^yF!~pp%7pGvdzYRq?CQq^x(=pkO#P17}VrynjQ5;#5hC~ZjpedY0=kNb& zqq8*YUs7O=A``imMaIPhJ~|Aam#UIlBg^)K6+UyXw(Pfb@Q{_D5CXdjisX$;28G zj})ZzDTO)h$N0P0pr#hF`}UjM76w4iTp*}TpD#AaeDOswFU-R1pV;%oR`r-l5^_yl zbmk`O0ZMS^ixX67Tdf}Qt9n$~9N9C*ChJD$^+XDzAC$)#*&aVJV^Eh_ z%9;^}5!0};6r-)a;^tE-{R${qF*vpsBT`okqEfw?JJ<(@i<_f^j8ALOif@tst3-(+bQ^y0}ewYR@u4XZWSN7F1^jbO4PYq}4uHslFp-r$K};;;K!+4TkS zF@RUzfPwFb3viAgD?S+^eWZn~uV`@4z(E6Dz)A>Tz4ii-Ro@QlY4Y&B6)7&K?W1198P(%~#*rS*~jq4g}N`~^6^`-aQxF_|no1$@YD_N+}`MfhsQ=C{~O zYI94F5olPN&E#G3sa(s&W!rbieNwiM;~#@wMSF$gmP?ElpUY_bGb>n;S&i80V%X9`!4Rmx;A<`TI^>Y_!+r+(RQ% z_ZQOhH0;n@DN-_G5qNvB-WbaUOO1ag_5o#(o^@JTj9>QL^bEMak9x z{M4#ozk(~$X2=sA?$6vBG-i2XgF%nNm60?DJy~G|^woVpwl#rhsW4Fc;#EDet9_`C zzR!8AX=tO0Zy4sZf0f<-hVV{hK`QqF&k>q;<-zp)4a1!F53}1p%xV8HyZytQ_7Ah$ zKg?sOrMpEx+$MuI$h3fl=_%Dpr@K20fYh!;WCXB=! zsD_!))H z=ER#wJklxRyF@&%Ge_JCuriEN))eEx*DkB+JYJZE+egbBvCDc+J*LkQFQ_?U3MDW{ zd?6_KQgZS8x%@^8Pe^U&lM7mD=kM&q^mbO0_AUSddqLLuo4cPq}+G@&`Lua?Zotk{*%P-qoMYG%QR;9 z25Y6jA1%CjU5Y$?adSOM2Q+P5KQj$g;srk=j{x-)}8 z?R<2kv~!;|TRoU@hK9CTL}K`!eedGO| zP)3W5le6qZp^WC8h#n+h>t_U#l< zy9CBJrO=1Hvn-xYqlHgifS;m4^ONoSm+XuVD}&r`q?Tox@CD^>Y=#uE{{gi{BMUP; zh2&hMt;|}Aq4$Xp94D^rryYZ#x>+db3Lix{2XLh@+Rirx`XFm1CZmX1Ef1)8UXy#IF&SFGchHC1Krl}h?5h)fZ zXBQ~ule!*q*89$Q7K;gSE6}}pP#de%upcF9M`Ly6!L}LgGQxQi8hlNe%u^e1^fV%udnWRx zOf`c|23Hu5$jyr1#{1W09@~hg>ClbA==I$Gd(+DkYAB5Bs`1Y)T++vJuX?QKah?x< zO?FYC6U^Z2PEm=M!Y{@9!QAA8%v99i$54!3+MyeN)4czjjDePn~+n!ap^`(6gs zsk>t>@!4Q+PT+)aZ9VQhJoA>v5cCjypzqH z!2^`{)|CE}`>*?JO9sxDBA)zGijaiGq51Ny^$;oeLtwvS@*7D8p;pu$D@HqY`nSbQ6cnI(yp1e5g?2mUKo0R

$Iwa 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 d9n(p#c7$`X?AnQP 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 z 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 literal 0 HcmV?d00001 diff --git a/DWARFutils/dwarfutils.py b/DWARFutils/dwarfutils.py new file mode 100644 index 00000000..b0850021 --- /dev/null +++ b/DWARFutils/dwarfutils.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +import os +import pickle +import re +import subprocess +import sys + +dwarfdump = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "dwarfdump") + + +def extract_dies_by_name(dwarffilepath, name, children=False, parents=False): + return extract_dies(["--name={}".format(name), dwarffilepath], children, parents) + + +def extract_dies_by_offset(dwarffilepath, offset, children=False, parents=False): + return extract_dies( + ["--debug-info={}".format(offset), dwarffilepath], children, parents + ) + + +def extract_dies(dwarfdumpargs, children, parents): + popenargs = [dwarfdump, "--verbose"] + dwarfdumpargs + if children: + popenargs.append("--show-children") + if parents: + popenargs.append("--show-parents") + stdout = subprocess.run(popenargs, stdout=subprocess.PIPE).stdout.decode("ascii") + dies = stdout.strip().split("\n\n")[1:] + return dies + + +def extract_uuid(dwarffilepath): + stdout = subprocess.run( + [dwarfdump, "--uuid", dwarffilepath], stdout=subprocess.PIPE + ).stdout.decode("ascii") + uuid = stdout.strip().split()[1] + return uuid + + +def _compute_cache_filepath(dwarffilepath): + return "/tmp/cache-{}.pickle".format(extract_uuid(dwarffilepath)) + + +def load_cache(dwarffilepath): + try: + with open(_compute_cache_filepath(dwarffilepath), "rb") as f: + DIEs = pickle.load(f) + return DIEs + except FileNotFoundError: + return {} + + +def save_cache(dwarffilepath, DIEs): + with open(_compute_cache_filepath(dwarffilepath), "wb") as f: + pickle.dump(DIEs, f) + + +re_bit_offset = re.compile(r"AT_data_bit_offset\( (0x[0-9a-f]+) \)") +re_bit_size = re.compile(r"AT_bit_size\( (0x[0-9a-f]+) \)") +re_byte_size = re.compile(r"AT_byte_size\( (0x[0-9a-f]+) \)") +re_const_value = re.compile(r"AT_const_value\( (0x[0-9a-f]+) \)") +re_count = re.compile(r"AT_count\( (0x[0-9a-f]+) \)") +re_decl_file = re.compile(r'AT_decl_file\( .*?"(.+?)" \)') +re_decl_line = re.compile(r"AT_decl_line\( .*?\( ([0-9]+) \) \)") +re_location = re.compile( + r"AT_data_member_location\( (?:.+?plus-uconst )?.+(0x[0-9a-f]+?) \)" +) +re_name = re.compile(r'AT_name\( .*?"(.+?)" \)') +re_offset = re.compile(r"^(0x[0-9a-f]+)\:") +re_tag = re.compile(r"(TAG\_.+?) ") +re_ttype = re.compile(r"AT_type\( .*?\{(0x[0-9a-f]+)\} \( +(.+?) +\) \)") + + +def extract_bit_size(textdie): + bit_size, = re_bit_size.search(textdie).groups() + bit_size = int(bit_size, 16) + return bit_size + + +def extract_byte_size(textdie): + byte_size, = re_byte_size.search(textdie).groups() + byte_size = int(byte_size, 16) + return byte_size + + +def extract_const_value(textdie): + const_value, = re_const_value.search(textdie).groups() + const_value = int(const_value, 16) + return const_value + + +def extract_count(textdie): + count, = re_count.search(textdie).groups() + count = int(count, 16) + return count + + +def extract_decl_file(textdie): + decl_file, = re_decl_file.search(textdie).groups() + return decl_file + + +def extract_decl_line(textdie): + decl_line, = re_decl_line.search(textdie).groups() + decl_line = int(decl_line) + return decl_line + + +def extract_location(textdie): + location, = re_location.search(textdie).groups() + location = int(location, 16) + return location + + +def extract_bit_location(textdie): + bit_offset, = re_bit_offset.search(textdie).groups() + bit_offset = int(bit_offset, 16) + location = (bit_offset // 0x8, bit_offset % 0x8) + return location + + +def extract_name(textdie): + name, = re_name.search(textdie).groups() + return name + + +def extract_offset(textdie): + offset, = re_offset.search(textdie).groups() + offset = int(offset, 16) + return offset + + +def extract_tag(textdie): + tag, = re_tag.search(textdie).groups() + return tag + + +def extract_type(textdie): + ttype_offset, ttype_name = re_ttype.search(textdie).groups() + ttype_offset = int(ttype_offset, 16) + return (ttype_offset, ttype_name) diff --git a/DWARFutils/misc/debug.sh b/DWARFutils/misc/debug.sh new file mode 100755 index 00000000..fbea3a82 --- /dev/null +++ b/DWARFutils/misc/debug.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -e +dirname () { python -c "import os; print(os.path.dirname(os.path.realpath('$0')))"; } +cd "$(dirname "$0")" + +DWARFUTILS_DWARFFILE="../../data/10-14-2-18C54/DWARF/kernel" + +: ${1?"Usage: $0 OFFSETS"} +OFFSETS="$*" + +echo "DWARFUTILS_DWARFFILE=\"$DWARFUTILS_DWARFFILE\"" +echo "OFFSETS=\"$OFFSETS\"" + +DWARFUTILS_SRCDIRECTORY=$(../parse-dwarf-types-to-c-source.py "$DWARFUTILS_DWARFFILE" $OFFSETS \ + | python -c 'import re, sys; print(re.search("Output directory: .(.+?).$", sys.stdin.read()).group(1))' ) +echo $DWARFUTILS_SRCDIRECTORY +cd "$DWARFUTILS_SRCDIRECTORY" >/dev/null + clang -g -x c -shared *.c + command -v clang-format >/dev/null && clang-format -i -style="{AlignConsecutiveDeclarations: true}" *.c +cd - 1>/dev/null diff --git a/DWARFutils/misc/test.sh b/DWARFutils/misc/test.sh new file mode 100755 index 00000000..b0291435 --- /dev/null +++ b/DWARFutils/misc/test.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e +dirname () { python -c "import os; print(os.path.dirname(os.path.realpath('$0')))"; } +cd "$(dirname "$0")" + +OFFSETS=( 0x00028090 0x00026D0E 0x000F0124 0x0002E494 0x0002E00B 0x00027DF6 0x0000E7E4 0x001EB43F 0x00DC1EA5 0x000272F1) +for OFFSET in "${OFFSETS[@]}" +do + echo "Testing $OFFSET" + ./debug.sh "$OFFSET" + echo +done diff --git a/DWARFutils/parse-dwarf-types-to-c-source.py b/DWARFutils/parse-dwarf-types-to-c-source.py new file mode 100755 index 00000000..3baca804 --- /dev/null +++ b/DWARFutils/parse-dwarf-types-to-c-source.py @@ -0,0 +1,698 @@ +#!/usr/bin/env python3 +import argparse +import collections +import dataclasses +import os +import re +import sys +import tempfile + +import dwarfutils + + +@dataclasses.dataclass +class DIE: + pass + + +@dataclasses.dataclass +class DIEArray(DIE): + ttype: tuple + size: int + + +def _extract_array_count(off): + txts = dwarfutils.extract_dies_by_offset(args.dwarffile.name, off, children=True) + assert dwarfutils.extract_tag(txts[0]) == "TAG_array_type" + assert dwarfutils.extract_tag(txts[1]) == "TAG_subrange_type" + try: + count = dwarfutils.extract_count(txts[1]) + except AttributeError: + count = -1 + return count + + +def parse_array_type(txt): + off = dwarfutils.extract_offset(txt) + type_off, type_name = dwarfutils.extract_type(txt) + extract_and_parse_die(type_off) + DIEs[off] = DIEArray(ttype=(type_off, type_name), size=_extract_array_count(off)) + + +@dataclasses.dataclass +class DIEBase(DIE): + name: str + byte_size: int + + +def parse_base_type(txt): + off = dwarfutils.extract_offset(txt) + DIEs[off] = DIEBase( + name=dwarfutils.extract_name(txt), byte_size=dwarfutils.extract_byte_size(txt) + ) + + +@dataclasses.dataclass +class DIECompileUnit(DIE): + name: str + + +def parse_compile_unit_type(txt): + off = dwarfutils.extract_offset(txt) + DIEs[off] = DIECompileUnit(name=dwarfutils.extract_name(txt)) + + +@dataclasses.dataclass +class DIEConst(DIE): + ttype: tuple + + +def parse_const_type(txt): + off = dwarfutils.extract_offset(txt) + try: + type_off, type_name = dwarfutils.extract_type(txt) + except AttributeError: + type_off, type_name = None, None + else: + extract_and_parse_die(type_off) + DIEs[off] = DIEConst(ttype=(type_off, type_name)) + + +@dataclasses.dataclass +class DIEEnumeration(DIE): + name: str + members: list + + +@dataclasses.dataclass +class DIEEnumerator(DIE): + name: str + const_value: int + + +def parse_enumeration_type(txt): + off = dwarfutils.extract_offset(txt) + try: + name = dwarfutils.extract_name(txt) + except AttributeError: + name = None + DIEs[off] = DIEEnumeration(name=name, members=list()) + parse_enumeration(off) + + +def parse_enumeration(parent_off): + txts = dwarfutils.extract_dies_by_offset( + args.dwarffile.name, parent_off, children=True + ) + assert dwarfutils.extract_tag(txts[0]) == "TAG_enumeration_type" + for txt in txts[1:]: + if "TAG_enumerator" in txt: + off = dwarfutils.extract_offset(txt) + DIEs[off] = DIEEnumerator( + name=dwarfutils.extract_name(txt), + const_value=dwarfutils.extract_const_value(txt), + ) + DIEs[parent_off].members.append(off) + elif "NULL" in txt: + return + else: + raise NotImplementedError + else: + assert "AT_declaration" in txts[0] + + +@dataclasses.dataclass +class DIEFormalParameter(DIE): + ttype: tuple + + +@dataclasses.dataclass +class DIEMember(DIE): + name: str + ttype: tuple + location: int + bit_size: int + + +def parse_member_type(txt): + off = dwarfutils.extract_offset(txt) + try: + name = dwarfutils.extract_name(txt) + except AttributeError: + name = None + type_off, type_name = dwarfutils.extract_type(txt) + extract_and_parse_die(type_off) + try: + location = dwarfutils.extract_location(txt) + except AttributeError: + try: + location = dwarfutils.extract_bit_location(txt) + except AttributeError: + location = -1 + try: + bit_size = dwarfutils.extract_bit_size(txt) + except AttributeError: + bit_size = -1 + DIEs[off] = DIEMember( + name=name, ttype=(type_off, type_name), location=location, bit_size=bit_size + ) + + +@dataclasses.dataclass +class DIEPointer(DIE): + ttype: tuple + + +def parse_pointer_type(txt): + off = dwarfutils.extract_offset(txt) + try: + type_off, type_name = dwarfutils.extract_type(txt) + except AttributeError: + type_off, type_name = None, None + else: + extract_and_parse_die(type_off) + DIEs[off] = DIEPointer(ttype=(type_off, type_name)) + + +@dataclasses.dataclass +class DIEStructure(DIE): + name: str + byte_size: int + members: list + + +def parse_structure_type(txt): + off = dwarfutils.extract_offset(txt) + try: + name = dwarfutils.extract_name(txt) + except AttributeError: + name = None + try: + byte_size = dwarfutils.extract_byte_size(txt) + except AttributeError: + assert "AT_declaration" in txt + byte_size = -1 + DIEs[off] = DIEStructure(name=name, byte_size=byte_size, members=list()) + parse_struct(off) + + +def parse_struct(parent_off): + txts = dwarfutils.extract_dies_by_offset( + args.dwarffile.name, parent_off, children=True + ) + level = 0 + for txt in txts[1:]: + if "TAG_member" in txt: + if level > 0: + # ignore + continue + parse_member_type(txt) + DIEs[parent_off].members.append(dwarfutils.extract_offset(txt)) + elif "NULL" in txt: + level -= 1 + if level < 0: + return + elif "TAG_union_type" in txt: + if not "AT_declaration" in txt: + level += 1 + if level == 0: + parse_union_type(txt) + elif "TAG_structure_type" in txt: + if not "AT_declaration" in txt: + level += 1 + if level == 0: + parse_structure_type(txt) + else: + raise NotImplementedError + else: + assert len(txts) == 1 or "AT_declaration" in txts[0] + + +@dataclasses.dataclass +class DIESubroutine(DIE): + ttype: tuple + members: list + + +def parse_subroutine_type(txt): + off = dwarfutils.extract_offset(txt) + try: + type_off, type_name = dwarfutils.extract_type(txt) + except AttributeError: + type_off, type_name = None, "void" + else: + extract_and_parse_die(type_off) + DIEs[off] = DIESubroutine(ttype=(type_off, type_name), members=list()) + parse_subroutine(off) + + +def parse_subroutine(parent_off): + txts = dwarfutils.extract_dies_by_offset( + args.dwarffile.name, parent_off, children=True + ) + assert dwarfutils.extract_tag(txts[0]) == "TAG_subroutine_type" + for txt in txts[1:]: + if "TAG_formal_parameter" in txt: + off = dwarfutils.extract_offset(txt) + type_off, type_name = dwarfutils.extract_type(txt) + extract_and_parse_die(type_off) + DIEs[off] = DIEFormalParameter(ttype=(type_off, type_name)) + DIEs[parent_off].members.append(off) + elif "TAG_unspecified_parameters" in txt: + pass + elif "NULL" in txt: + return + else: + raise NotImplementedError + + +@dataclasses.dataclass +class DIETypedef(DIE): + name: str + ttype: tuple + + +def parse_typedef(txt): + off = dwarfutils.extract_offset(txt) + name = dwarfutils.extract_name(txt) + if "__builtin_va_list" in name: + name = name.upper() + type_off, type_name = dwarfutils.extract_type(txt) + extract_and_parse_die(type_off) + DIEs[off] = DIETypedef(name=name, ttype=(type_off, type_name)) + + +@dataclasses.dataclass +class DIEUnion(DIE): + name: str + byte_size: int + members: list + + +def parse_union_type(txt): + off = dwarfutils.extract_offset(txt) + try: + name = dwarfutils.extract_name(txt) + except AttributeError: + name = None + byte_size = dwarfutils.extract_byte_size(txt) + DIEs[off] = DIEUnion(name=name, byte_size=byte_size, members=list()) + parse_union(off) + + +def parse_union(parent_off): + parse_struct(parent_off) + + +@dataclasses.dataclass +class DIEVariable(DIE): + name: str + ttype: tuple + + +def parse_variable_type(txt): + off = dwarfutils.extract_offset(txt) + name = dwarfutils.extract_name(txt) + type_off, type_name = dwarfutils.extract_type(txt) + extract_and_parse_die(type_off) + DIEs[off] = DIEVariable(name=name, ttype=(type_off, type_name)) + + +@dataclasses.dataclass +class DIEVolatile(DIE): + ttype: tuple + + +def parse_volatile_type(txt): + off = dwarfutils.extract_offset(txt) + type_off, type_name = dwarfutils.extract_type(txt) + extract_and_parse_die(type_off) + DIEs[off] = DIEVolatile(ttype=(type_off, type_name)) + + +def find_compile_unit(off): + txts = dwarfutils.extract_dies_by_offset(args.dwarffile.name, off, parents=True) + assert "TAG_compile_unit" in txts[0] + cu_off = dwarfutils.extract_offset(txts[0]) + return cu_off + + +def extract_and_parse_die(off): + if off in DIEs: + print("Using cached {}".format(DIEs[off]), file=sys.stderr) + return + + txts = dwarfutils.extract_dies_by_offset(args.dwarffile.name, off) + txt = txts[0] + print(txt, file=sys.stderr) + assert dwarfutils.extract_offset(txt) == off + + handlers = { + "TAG_array_type": parse_array_type, + "TAG_base_type": parse_base_type, + "TAG_compile_unit": parse_compile_unit_type, + "TAG_const_type": parse_const_type, + "TAG_enumeration_type": parse_enumeration_type, + "TAG_member": parse_member_type, + "TAG_pointer_type": parse_pointer_type, + "TAG_structure_type": parse_structure_type, + "TAG_subroutine_type": parse_subroutine_type, + "TAG_typedef": parse_typedef, + "TAG_union_type": parse_union_type, + "TAG_variable": parse_variable_type, + "TAG_volatile_type": parse_volatile_type, + } + tag = dwarfutils.extract_tag(txt) + try: + return handlers[tag](txt) + except KeyError: + raise NotImplementedError + + +def _compute_deps(off, deps_cache, ptroffs): + if off in deps_cache: + return deps_cache[off] + die = DIEs[off] + + deps = [off] + + if any((isinstance(die, DIEBase), isinstance(die, DIEEnumeration))): + deps_cache[off] = deps + return deps + + elif any( + ( + isinstance(die, DIEArray), + isinstance(die, DIEConst), + isinstance(die, DIEFormalParameter), + isinstance(die, DIEMember), + isinstance(die, DIETypedef), + isinstance(die, DIEVariable), + isinstance(die, DIEVolatile), + ) + ): + type_off, type_name = die.ttype + if type_off and type_name: + deps.extend(_compute_deps(type_off, deps_cache, ptroffs)) + + deps_cache[off] = deps + return deps + + elif any( + ( + isinstance(die, DIEStructure), + isinstance(die, DIESubroutine), + isinstance(die, DIEUnion), + ) + ): + if isinstance(die, DIESubroutine): + type_off, type_name = die.ttype + if type_off and type_name: + deps.extend(_compute_deps(type_off, deps_cache, ptroffs)) + + for mem_off in die.members: + deps.extend(_compute_deps(mem_off, deps_cache, ptroffs)) + + deps_cache[off] = deps + return deps + + elif isinstance(die, DIEPointer): + if off in ptroffs: + # cycling + return deps + + type_off, type_name = die.ttype + if type_off and type_name: + ptroffs = ptroffs | {off} + deps.extend(_compute_deps(type_off, deps_cache, ptroffs)) + + deps_cache[off] = deps + return deps + + else: + raise NotImplementedError + + +def _resolve_type(die): + try: + type_off, type_name = die.ttype + except AttributeError: + child_off = None + else: + if type_off: + # TODO this is for void* + child_off = DIEs[type_off] + else: + child_off = None + + if isinstance(die, DIEArray): + return "{}[{}]".format( + _resolve_type(child_off), die.size if die.size >= 0 else "" + ) + + elif isinstance(die, DIEBase): + return die.name + + elif isinstance(die, DIEConst): + return "const {}".format(_resolve_type(child_off) if child_off else "void") + + elif isinstance(die, DIEEnumeration): + if die.name: + return "enum {}".format(die.name) + else: + members = "\n".join( + "{} = {},".format(DIEs[mem_off].name, DIEs[mem_off].const_value) + for mem_off in die.members + ) + return "enum {{\n{}\n}}".format(members) + + elif isinstance(die, DIEFormalParameter): + return _resolve_type(child_off) + + elif isinstance(die, DIEMember): + return _resolve_type(child_off) + + elif isinstance(die, DIEPointer): + if not child_off: + return "void*" + return "{}*".format(_resolve_type(child_off)) + + elif any((isinstance(die, DIEStructure), isinstance(die, DIEUnion))): + dtype = "struct" if isinstance(die, DIEStructure) else "union" + if die.name: + return "{} {}".format(dtype, die.name) + else: + members = "\n".join( + _generate_declaration(mem_off) for mem_off in die.members + ) + return "{} {{\n{}\n}}".format(dtype, members) + + elif isinstance(die, DIESubroutine): + members = ",".join( + _generate_declaration(mem_off).replace(";", "") for mem_off in die.members + ) + return "{} ()({})".format( + _resolve_type(child_off) if child_off else "void", members + ) + + elif isinstance(die, DIETypedef): + return die.name + + elif isinstance(die, DIEVariable): + return _resolve_type(child_off) + + elif isinstance(die, DIEVolatile): + return "volatile {}".format(_resolve_type(child_off)) + + raise NotImplementedError + + +def _member_off(die): + try: + return "/* off=0x{:04x} */".format(die.location) + except AttributeError: + return "" + except TypeError: + return "/* off=0x{:04x} bit={} */".format(die.location[0], die.location[1]) + + +def _dwarf_off(off): + return "/* die=0x{:x} */".format(off) + + +def _struct_info(off): + return "/* size=0x{:x} die=0x{:x} */".format(DIEs[off].byte_size, off) + + +def _generate_declaration(off): + die = DIEs[off] + type_str = _resolve_type(die) + + try: + name = die.name + except AttributeError: + assert isinstance(die, DIEFormalParameter) + name = "" + else: + if name is None: + assert isinstance(die, DIEMember) + # unnamed struct, union, enum + name = "" + + try: + bit_size = die.bit_size + except AttributeError: + pass + else: + if bit_size >= 0: + assert isinstance(die, DIEMember) + return "{} {}:{}; {}".format(type_str, name, bit_size, _member_off(die)) + + try: + array_size_with_brackets, = re.search(r"(\[\d*\])$", type_str).groups() + except AttributeError: + pass + else: + return "{} {}{}; {}".format( + type_str.replace(array_size_with_brackets, ""), + name, + array_size_with_brackets, + _member_off(die), + ) + + try: + ret_type_str, params, ptrs = re.search( + r"(.*?) *\(\)\((.*?)\)(\**)$", type_str + ).groups() + except AttributeError: + pass + else: + return "{} ({}{})({}); {}".format( + ret_type_str, ptrs, name, params, _member_off(die) + ) + + return "{} {}; {}".format(type_str, name, _member_off(die)) + + +def _generate_definition(off): + die = DIEs[off] + + if isinstance(die, DIEEnumeration): + assert die.name + members = "\n".join( + "{} = {},".format(DIEs[mem_off].name, DIEs[mem_off].const_value) + for mem_off in die.members + ) + return "enum {} {{\n{}\n}}; {}".format(die.name, members, _dwarf_off(off)) + + elif any((isinstance(die, DIEStructure), isinstance(die, DIEUnion))): + assert die.name + dtype = "struct" if isinstance(die, DIEStructure) else "union" + if die.byte_size == -1: + return "{} {}; {}".format(dtype, die.name, _dwarf_off(off)) + members = "\n".join(_generate_declaration(mem_off) for mem_off in die.members) + return "{} {} {{\n{}\n}}; {}".format( + dtype, die.name, members, _struct_info(off) + ) + + elif isinstance(die, DIETypedef): + type_off, _ = die.ttype + child_off = DIEs[type_off] + + child_type_str = _resolve_type(child_off) + try: + array_size_with_brackets, = re.search( + r"(\[\d*\])$", child_type_str + ).groups() + except AttributeError: + pass + else: + return "typedef {} {}{}; {}".format( + child_type_str.replace(array_size_with_brackets, ""), + die.name, + array_size_with_brackets, + _dwarf_off(off), + ) + + try: + ret_type_str, params, ptrs = re.search( + r"(.+?) \(\)\((.*?)\)(\*?)$", child_type_str + ).groups() + except AttributeError: + pass + else: + return "typedef {} ({}{})({}); {}".format( + ret_type_str, ptrs, die.name, params, _dwarf_off(off) + ) + + return "typedef {} {}; {}".format(child_type_str, die.name, _dwarf_off(off)) + + elif isinstance(die, DIEVariable): + return _generate_declaration(off) + + raise NotImplementedError + + +def generate_c_source(offs): + deps_cache = {} + deps = list() + for off in offs: + curr_deps = _compute_deps(off, deps_cache, set()) + curr_deps = list(reversed(curr_deps)) + deps = curr_deps + deps + # remove duplicates maintaining the order + deps = list(collections.OrderedDict.fromkeys(deps)) + + csource = "" + for off in deps: + die = DIEs[off] + if ( + not any( + ( + isinstance(die, DIEEnumeration), + isinstance(die, DIEStructure), + isinstance(die, DIETypedef), + isinstance(die, DIEUnion), + isinstance(die, DIEVariable), + ) + ) + or die.name is None + ): + continue + csource += "{}\n\n".format(_generate_definition(off)) + return csource + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("dwarffile", type=argparse.FileType()) + parser.add_argument("offset", type=lambda n: int(n, 0), nargs="+") + args = parser.parse_args() + + DIEs = dwarfutils.load_cache(args.dwarffile.name) + + # extract and parse DIEs at the specified offsets + print("Extracting DIEs...") + CUs = collections.defaultdict(set) + for off in args.offset: + cu_off = find_compile_unit(off) + extract_and_parse_die(cu_off) + CUs[cu_off].add(off) + + extract_and_parse_die(off) + + dwarfutils.save_cache(args.dwarffile.name, DIEs) + + # generate C source files, one per CU + print("Generating C sources...") + outdirpath = tempfile.mkdtemp() + for cu_off, offs in CUs.items(): + cu = DIEs[cu_off] + # assume no multiple CUs with the same basename... + cfilename = os.path.basename(cu.name) + with open(os.path.join(outdirpath, cfilename), "w") as f: + csource = generate_c_source(offs) + f.write(csource) + + # print the output directory so that other scripts know where to look + print("Output directory: '{}'".format(outdirpath)) diff --git a/DWARFutils/relocate-dwarf-variable.py b/DWARFutils/relocate-dwarf-variable.py new file mode 100755 index 00000000..9049dbaf --- /dev/null +++ b/DWARFutils/relocate-dwarf-variable.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +import argparse +import re +import subprocess + +import dwarfutils + + +def extract_var_info(dwarffilepath, varname): + textdies = dwarfutils.extract_dies_by_name(dwarffilepath, varname) + for textdie in textdies: + try: + location, b, addr = re.search( + r"AT_location\( \<(0x[0-9a-f]+)> ([0-9a-f]+) .+? \( addr (0x[0-9a-f]+)", + textdie, + ).groups() + location = int(location, 16) + b = int(b, 16) + addr = int(addr, 16) + except AttributeError: + continue + else: + # first valid occurrence seems to be always the correct one + return location, b, addr + else: + raise AssertionError + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("dwarffile", type=argparse.FileType()) + parser.add_argument("varname") + parser.add_argument("newaddr", type=lambda i: int(i, 0)) + args = parser.parse_args() + + location, b, curr_addr = extract_var_info(args.dwarffile.name, args.varname) + curr_addr_len_in_bytes = 8 + bytes_to_find_in_dwarffile = b"%b%b%b" % ( + location.to_bytes(1, byteorder="little"), + b.to_bytes(1, byteorder="little"), + curr_addr.to_bytes(curr_addr_len_in_bytes, byteorder="little"), + ) + + with open(args.dwarffile.name, "rb") as f: + dwarffile = f.read() + + dwarffile_addr_offset = ( + dwarffile.find(bytes_to_find_in_dwarffile) + + len(bytes_to_find_in_dwarffile) + - curr_addr_len_in_bytes + ) + + dwarffile_addr_value = int.from_bytes( + dwarffile[ + dwarffile_addr_offset : dwarffile_addr_offset + curr_addr_len_in_bytes + ], + byteorder="little", + ) + assert dwarffile_addr_value == curr_addr + + new_addr = args.newaddr.to_bytes(8, byteorder="little") + + new_dwarffile = bytearray(dwarffile) + new_dwarffile[ + dwarffile_addr_offset : dwarffile_addr_offset + len(new_addr) + ] = new_addr + with open(args.dwarffile.name, "wb") as f: + f.write(new_dwarffile) diff --git a/FDPutils/FDP/FDP.c b/FDPutils/FDP/FDP.c new file mode 100644 index 00000000..b3230995 --- /dev/null +++ b/FDPutils/FDP/FDP.c @@ -0,0 +1,1358 @@ +/* + MIT License + + Copyright (c) 2015 Nicolas Couffin ncouffin@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include +#include +#include +#include +#ifndef __cplusplus +#include +#endif +#include +#include +#include + +#include "FDP.h" +#include "FDP_structs.h" + +#ifdef __linux__ +#include +#include +#include +#endif + + +void* CreateSHM(char *name, int size) +{ + int fd = shm_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + if (fd == -1) + { + perror("create: shm_open: fail\n"); + return NULL; + } + + ftruncate(fd, size); + + void *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (buf == MAP_FAILED) + { + printf("create: mmap: fail\n"); + shm_unlink(name); + close(fd); + return NULL; + } + + close(fd); + return buf; +} + +void* OpenSHM(const char *name, size_t size) +{ + int fd = shm_open(name, O_RDWR, S_IRUSR | S_IWUSR); + if (fd == -1) + { + perror("oopen: shm_open: fail\n"); + return NULL; + } + + void *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (buf == MAP_FAILED) + { + printf("oopen: mmap: fail\n"); + close(fd); + return NULL; + } + + close(fd); + return buf; +} + + +#define MIN(a,b) (((a)<(b))?(a):(b)) + +#define FDP_POWER_SAVE 1 + +__inline static void ttas_spinlock_lock(volatile bool* lock) +{ + uint16_t test_counter = 0; + do{ + if (*lock == false) + { + test_counter = 0; + //tested... and __sync_bool_compare_and_swap... is slower... + if (__sync_val_compare_and_swap(lock, false, true) == false) + { + //MemoryBarrier(); + __sync_synchronize(); + return; + } + } + else + { + if (test_counter == 0xFFFF) + { +#if FDP_POWER_SAVE == 1 + usleep(10 * 1000); +#endif + } + else + { + test_counter++; + } + } + }while(true); +} + +__inline static void ttas_spinlock_unlock(volatile bool* lock) +{ + *lock = false; + __sync_synchronize(); + //MemoryBarrier(); + return; +} + +#define LockSHM(x) ttas_spinlock_lock(&x->lock); +/*__inline static void LockSHM(FDP_SHM_SHARED* FDPShm) +{ + ttas_spinlock_lock(&FDPShm->lock); +}*/ + +#define UnlockSHM(x) ttas_spinlock_unlock(&x->lock); +/*__inline static void UnlockSHM(FDP_SHM_SHARED* FDPShm) +{ + ttas_spinlock_unlock(&FDPShm->lock); +}*/ + +static bool WriteFDPDataWithStatus(FDP_SHM_CANAL* pFDPCanal, uint8_t* pData, uint32_t DataSize, bool bStatus) +{ + bool dataWritten = false; + if (DataSize > FDP_MAX_DATA_SIZE) + { + return false; + } + do + { + ttas_spinlock_lock(&pFDPCanal->lock); + if (pFDPCanal->bDataPresent == false) + { + __builtin_memcpy((char*)pFDPCanal->data, pData, DataSize); + pFDPCanal->bDataPresent = true; + pFDPCanal->dataSize = DataSize; + pFDPCanal->bStatus = bStatus; + dataWritten = true; + } + ttas_spinlock_unlock(&pFDPCanal->lock); + } + while (dataWritten == false); + return true; +} + +static bool WriteFDPData(FDP_SHM_CANAL* pFDPCanal, uint8_t* pData, uint32_t DataSize) +{ + return WriteFDPDataWithStatus(pFDPCanal, pData, DataSize, true); +} + +static uint32_t ReadFDPDataWithStatus(FDP_SHM_CANAL* pFDPCanal, uint8_t* buffer, bool* pbStatus) +{ + bool dataRead = false; + uint32_t dataReadSize = 0; + uint32_t readTry = 0; + do + { + if (pFDPCanal->bDataPresent) + { + ttas_spinlock_lock(&pFDPCanal->lock); + if (pFDPCanal->bDataPresent) //Verification + { + if (pFDPCanal->dataSize < FDP_MAX_DATA_SIZE) + { + __builtin_memcpy(buffer, (char*)pFDPCanal->data, pFDPCanal->dataSize); + } + pFDPCanal->bDataPresent = false; //All data is read ! + dataRead = true; + dataReadSize = pFDPCanal->dataSize; + *pbStatus = pFDPCanal->bStatus; + readTry = 0; + } + ttas_spinlock_unlock(&pFDPCanal->lock); + } + if ((readTry & 0xFFFFFF) == 0xFFFFFF) + { +#if FDP_POWER_SAVE == 1 + usleep(10 * 1000); +#endif + } + else + { + readTry++; + } + } + while (dataRead == false); + return dataReadSize; +} + +__inline static uint32_t ReadFDPData(FDP_SHM_CANAL* pFDPCanal, uint8_t* buffer) +{ + bool bIsSuccess; + return ReadFDPDataWithStatus(pFDPCanal, buffer, &bIsSuccess); +} + +FDP_EXPORTED +FDP_SHM* FDP_CreateSHM(char* shmName) +{ + void *pBuf = CreateSHM(shmName, FDP_SHM_SHARED_SIZE); + if (pBuf == NULL) { + return NULL; + } + //Clear SHM + memset(pBuf, 0, FDP_SHM_SHARED_SIZE); + FDP_SHM* pFDPSHM = (FDP_SHM*)malloc(sizeof(FDP_SHM)); + //TODO: check ! + pFDPSHM->pSharedFDPSHM = (FDP_SHM_SHARED*)pBuf; + return pFDPSHM; +} + +FDP_EXPORTED FDP_SHM* FDP_OpenSHM(const char* pShmName) +{ + void* pSharedFDPSHM = OpenSHM(pShmName, FDP_SHM_SHARED_SIZE); + if (pSharedFDPSHM == NULL) + { + return NULL; + } + //TODO : ! + char aCpuShmName[512] = {0}; + strcpy(aCpuShmName, "CPU_"); + strcat(aCpuShmName, pShmName); + + void* pCpuShm = OpenSHM(aCpuShmName, sizeof(FDP_CPU_CTX)); + if (pCpuShm == NULL) + { + printf("Failed to OpenShm(%s)\n", aCpuShmName); + return NULL; + } + FDP_SHM* pFDPSHM = (FDP_SHM*)malloc(sizeof(FDP_SHM)); + if (pFDPSHM == NULL) + { + //TODO : CloseShm + return NULL; + } + pFDPSHM->pSharedFDPSHM = (FDP_SHM_SHARED*)pSharedFDPSHM; + pFDPSHM->pCpuShm = (FDP_CPU_CTX *)pCpuShm; + return pFDPSHM; +} + + +FDP_EXPORTED +bool FDP_Pause(FDP_SHM* pFDP) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = false; + uint32_t InputBufferSize = 0; + FDP_SIMPLE_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_PAUSE_VM; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(TempPkt)); + InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + +FDP_EXPORTED +bool FDP_Resume(FDP_SHM* pFDP) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = false; + uint32_t InputBufferSize = 0; + FDP_SIMPLE_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_RESUME_VM; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(TempPkt)); + InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + +FDP_EXPORTED +bool FDP_Reboot(FDP_SHM* pFDP) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = false; + uint32_t InputBufferSize = 0; + FDP_SIMPLE_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_REBOOT; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(TempPkt)); + InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + +bool FDP_ReadPhysicalMemoryInternal(FDP_SHM* pFDP, uint8_t* pDstBuffer, uint32_t ReadSize, uint64_t PhysicalAddress) +{ + uint64_t ReceivedSize = 0; + bool bReturnCode = false; + FDP_READ_PHYSICAL_MEMORY_PKT_REQ tmpPkt; + tmpPkt.Type = FDPCMD_READ_PHYSICAL; + tmpPkt.PhysicalAddress = PhysicalAddress; + tmpPkt.ReadSize = ReadSize; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&tmpPkt, sizeof(FDP_READ_PHYSICAL_MEMORY_PKT_REQ)); + ReceivedSize = ReadFDPDataWithStatus(&pFDP->pSharedFDPSHM->ServerToClient, pDstBuffer, &bReturnCode); + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnCode; +} + +FDP_EXPORTED +bool FDP_ReadPhysicalMemory(FDP_SHM* pFDP, uint8_t* pDstBuffer, uint32_t ReadSize, uint64_t PhysicalAddress) +{ + if (pFDP == NULL) + { + return false; + } + uint32_t CurrentOffset = 0; + do + { + uint32_t CurrentReadSize = MIN(ReadSize, FDP_MAX_DATA_SIZE - 1); + if (FDP_ReadPhysicalMemoryInternal(pFDP, pDstBuffer + CurrentOffset, CurrentReadSize, + PhysicalAddress + CurrentOffset) == false) + { + return false; + } + CurrentOffset += CurrentReadSize; + } + while (CurrentOffset < ReadSize); + return true; +} + +bool FDP_ReadVirtualMemoryInternal(FDP_SHM* pFDP, uint32_t CpuId, uint8_t* pDstBuffer, uint32_t ReadSize, + uint64_t VirtualAddress) +{ + uint32_t ReceivedSize = 0; + bool bReturnCode = false; + FDP_READ_VIRTUAL_MEMORY_PKT_REQ tmpPkt; + tmpPkt.Type = FDPCMD_READ_VIRTUAL; + tmpPkt.CpuId = CpuId; + tmpPkt.VirtualAddress = VirtualAddress; + tmpPkt.ReadSize = ReadSize; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&tmpPkt, sizeof(FDP_READ_PHYSICAL_MEMORY_PKT_REQ)); + ReceivedSize = ReadFDPDataWithStatus(&pFDP->pSharedFDPSHM->ServerToClient, pDstBuffer, &bReturnCode); + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnCode; +} + +FDP_EXPORTED +bool FDP_ReadVirtualMemory(FDP_SHM* pFDP, uint32_t CpuId, uint8_t* pDstBuffer, uint32_t ReadSize, + uint64_t VirtualAddress) +{ + if (pFDP == NULL || ReadSize <= 0) + { + return false; + } + uint32_t CurrentOffset = 0; + do + { + uint32_t CurrentReadSize = MIN(ReadSize, FDP_MAX_DATA_SIZE - 1); + if (FDP_ReadVirtualMemoryInternal(pFDP, CpuId, pDstBuffer + CurrentOffset, CurrentReadSize, + VirtualAddress + CurrentOffset) == false) + { + return false; + } + CurrentOffset += CurrentReadSize; + } + while (CurrentOffset < ReadSize); + return true; +} + +FDP_EXPORTED +bool FDP_WritePhysicalMemory(FDP_SHM* pFDP, uint8_t* pSrcBuffer, uint32_t WriteSize, uint64_t PhysicalAddress) +{ + if (pFDP == NULL) + { + return false; + } + uint32_t InputBufferSize = 0; + bool bReturnValue = false; + LockSHM(pFDP->pSharedFDPSHM); + { + FDP_WRITE_PHYSICAL_MEMORY_PKT_REQ* TempPkt = (FDP_WRITE_PHYSICAL_MEMORY_PKT_REQ*)pFDP->OutputBuffer; + TempPkt->Type = FDPCMD_WRITE_PHYSICAL; + TempPkt->PhysicalAddress = PhysicalAddress; + TempPkt->WriteSize = WriteSize; + if (WriteSize < FDP_MAX_DATA_SIZE - sizeof(*TempPkt)) + { + memcpy(TempPkt->Data, pSrcBuffer, WriteSize); + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)TempPkt, + sizeof(FDP_READ_PHYSICAL_MEMORY_PKT_REQ) + WriteSize); + InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); + } + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + + +FDP_EXPORTED +bool FDP_WriteVirtualMemory(FDP_SHM* pFDP, uint32_t CpuId, uint8_t* pSrcBuffer, uint32_t WriteSize, + uint64_t VirtualAddress) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = false; + uint32_t InputBufferSize = 0; + LockSHM(pFDP->pSharedFDPSHM); + { + FDP_WRITE_VIRTUAL_MEMORY_PKT_REQ* TempPkt = (FDP_WRITE_VIRTUAL_MEMORY_PKT_REQ*)pFDP->OutputBuffer; + TempPkt->Type = FDPCMD_WRITE_VIRTUAL; + TempPkt->CpuId = CpuId; + TempPkt->VirtualAddress = VirtualAddress; + TempPkt->WriteSize = WriteSize; + if (WriteSize < FDP_MAX_DATA_SIZE - sizeof(*TempPkt)) + { + memcpy(TempPkt->Data, pSrcBuffer, WriteSize); + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, pFDP->OutputBuffer, + sizeof(FDP_WRITE_VIRTUAL_MEMORY_PKT_REQ) + WriteSize); + InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); + } + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + +FDP_EXPORTED +uint64_t FDP_SearchPhysicalMemory(FDP_SHM* pFDP, const void* pPatternData, uint32_t PatternSize, uint64_t StartOffset) +{ + if (pFDP == NULL) + { + return false; + } + uint64_t FoundAddress = 0x0; + LockSHM(pFDP->pSharedFDPSHM); + { + FDP_SEARCH_PHYSICAL_MEMORY_PKT_REQ* TempPkt = (FDP_SEARCH_PHYSICAL_MEMORY_PKT_REQ*)pFDP->OutputBuffer; + TempPkt->Type = FDPCMD_SEARCH_PHYSICAL_MEMORY; + TempPkt->PatternSize = PatternSize; + TempPkt->StartOffset = StartOffset; + memcpy(TempPkt->PatternData, pPatternData, PatternSize); + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, pFDP->OutputBuffer, + sizeof(FDP_SEARCH_PHYSICAL_MEMORY_PKT_REQ) + PatternSize); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&FoundAddress); //TODO: return success/fail ! + } + UnlockSHM(pFDP->pSharedFDPSHM); + return FoundAddress; +} + +//TODO ! +FDP_EXPORTED +bool FDP_SearchVirtualMemory(FDP_SHM* pFDP, uint32_t CpuId, const void* pPatternData, uint32_t PatternSize, + uint64_t StartOffset) +{ + if (pFDP == NULL) + { + return false; + } + uint64_t FoundAddress = 0x0; + bool bReturnCode = false; + LockSHM(pFDP->pSharedFDPSHM); + { + FDP_SEARCH_VIRTUAL_MEMORY_PKT_REQ* TempPkt = (FDP_SEARCH_VIRTUAL_MEMORY_PKT_REQ*)pFDP->OutputBuffer; + TempPkt->Type = FDPCMD_SEARCH_VIRTUAL_MEMORY; + TempPkt->CpuId = CpuId; + TempPkt->PatternSize = PatternSize; + TempPkt->StartOffset = StartOffset; + memcpy(TempPkt->PatternData, pPatternData, PatternSize); + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, pFDP->OutputBuffer, + sizeof(FDP_SEARCH_VIRTUAL_MEMORY_PKT_REQ) + PatternSize); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&FoundAddress); //TODO: return success/fail ! + bReturnCode = true; + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnCode; +} + +FDP_EXPORTED +bool FDP_ReadRegister(FDP_SHM* pFDP, uint32_t CpuId, FDP_Register RegisterId, uint64_t* pRegisterValue) +{ + if (pFDP == NULL) + { + return false; + } + //Fast way... + switch (RegisterId) + { + case FDP_RIP_REGISTER: + *pRegisterValue = pFDP->pCpuShm->rip; + return true; + case FDP_RAX_REGISTER: + *pRegisterValue = pFDP->pCpuShm->rax; + return true; + case FDP_RCX_REGISTER: + *pRegisterValue = pFDP->pCpuShm->rcx; + return true; + case FDP_RDX_REGISTER: + *pRegisterValue = pFDP->pCpuShm->rdx; + return true; + case FDP_RBX_REGISTER: + *pRegisterValue = pFDP->pCpuShm->rbx; + return true; + case FDP_RSP_REGISTER: + *pRegisterValue = pFDP->pCpuShm->rsp; + return true; + case FDP_RBP_REGISTER: + *pRegisterValue = pFDP->pCpuShm->rbp; + return true; + case FDP_RSI_REGISTER: + *pRegisterValue = pFDP->pCpuShm->rsi; + return true; + case FDP_RDI_REGISTER: + *pRegisterValue = pFDP->pCpuShm->rdi; + return true; + case FDP_R8_REGISTER: + *pRegisterValue = pFDP->pCpuShm->r8; + return true; + case FDP_R9_REGISTER: + *pRegisterValue = pFDP->pCpuShm->r9; + return true; + case FDP_R10_REGISTER: + *pRegisterValue = pFDP->pCpuShm->r10; + return true; + case FDP_R11_REGISTER: + *pRegisterValue = pFDP->pCpuShm->r11; + return true; + case FDP_R12_REGISTER: + *pRegisterValue = pFDP->pCpuShm->r12; + return true; + case FDP_R13_REGISTER: + *pRegisterValue = pFDP->pCpuShm->r13; + return true; + case FDP_R14_REGISTER: + *pRegisterValue = pFDP->pCpuShm->r14; + return true; + case FDP_R15_REGISTER: + *pRegisterValue = pFDP->pCpuShm->r15; + return true; + case FDP_CR0_REGISTER: + *pRegisterValue = pFDP->pCpuShm->cr0; + return true; + case FDP_CR2_REGISTER: + *pRegisterValue = pFDP->pCpuShm->cr2; + return true; + case FDP_CR3_REGISTER: + *pRegisterValue = pFDP->pCpuShm->cr3; + return true; + case FDP_CR4_REGISTER: + *pRegisterValue = pFDP->pCpuShm->cr4; + return true; + default: + break; + } + //Old version => low performance + FDP_READ_REGISTER_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_READ_REGISTER; + TempPkt.CpuId = CpuId; + TempPkt.RegisterId = RegisterId; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(FDP_READ_REGISTER_PKT_REQ)); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)pRegisterValue); //TODO: return success/fail ! + } + UnlockSHM(pFDP->pSharedFDPSHM); + return true; +} + +FDP_EXPORTED +bool FDP_ReadMsr(FDP_SHM* pFDP, uint32_t CpuId, uint64_t MsrId, uint64_t* pMsrValue) +{ + if (pFDP == NULL) + { + return false; + } + FDP_READ_MSR_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_READ_MSR; + TempPkt.CpuId = CpuId; + TempPkt.MsrId = MsrId; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(FDP_READ_MSR_PKT_REQ)); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)pMsrValue); //TODO: return success/fail ! + } + UnlockSHM(pFDP->pSharedFDPSHM); + return true; +} + +FDP_EXPORTED +bool FDP_WriteMsr(FDP_SHM* pFDP, uint32_t CpuId, uint64_t MsrId, uint64_t MsrValue) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = false; + uint32_t InputBufferSize = 0; + FDP_WRITE_MSR_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_WRITE_MSR; + TempPkt.CpuId = CpuId; + TempPkt.MsrId = MsrId; + TempPkt.MsrValue = MsrValue; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(FDP_WRITE_MSR_PKT_REQ)); + InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + +FDP_EXPORTED +bool FDP_WriteRegister(FDP_SHM* pFDP, uint32_t CpuId, FDP_Register RegisterId, uint64_t RegisterValue) +{ + if (pFDP == NULL) + { + return false; + } + uint32_t InputBufferSize = 0; + bool bReturnValue = false; + FDP_WRITE_REGISTER_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_WRITE_REGISTER; + TempPkt.CpuId = CpuId; + TempPkt.RegisterId = RegisterId; + TempPkt.RegisterValue = RegisterValue; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(FDP_WRITE_REGISTER_PKT_REQ)); + InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + + +FDP_EXPORTED +bool FDP_UnsetBreakpoint(FDP_SHM* pFDP, uint8_t BreakpointId) +{ + if (pFDP == NULL) + { + return false; + } + uint32_t InputBufferSize = 0; + bool bReturnValue = false; + FDP_CLEAR_BREAKPOINT_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_UNSET_BP; + TempPkt.BreakpointId = BreakpointId; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(FDP_CLEAR_BREAKPOINT_PKT_REQ)); + InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + + +FDP_EXPORTED +int FDP_SetBreakpoint( + FDP_SHM* pFDP, + uint32_t CpuId, + FDP_BreakpointType BreakpointType, + uint8_t BreakpointId, + FDP_Access BreakpointAccessType, + FDP_AddressType BreakpointAddressType, + uint64_t BreakpointAddress, + uint64_t BreakpointLength, + uint64_t BreakpointCr3) +{ + if (pFDP == NULL) + { + return false; + } + uint32_t InputBufferSize = 0; + int iReturnedBreakpointId; + FDP_SET_BREAKPOINT_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_SET_BP; + TempPkt.CpuId = CpuId; + TempPkt.BreakpointType = BreakpointType; + TempPkt.BreakpointId = BreakpointId; + TempPkt.BreakpointAccessType = BreakpointAccessType; + TempPkt.BreakpointAddressType = BreakpointAddressType; + TempPkt.BreakpointAddress = BreakpointAddress; + TempPkt.BreakpointLength = BreakpointLength; + TempPkt.BreakpointCr3 = BreakpointCr3; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(FDP_SET_BREAKPOINT_PKT_REQ)); + InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&iReturnedBreakpointId); + } + UnlockSHM(pFDP->pSharedFDPSHM); + return iReturnedBreakpointId; +} + +FDP_EXPORTED +bool FDP_VirtualToPhysical(FDP_SHM* pFDP, uint32_t CpuId, uint64_t VirtualAddress, uint64_t* PhysicalAddress) +{ + if (pFDP == NULL) + { + return false; + } + FDP_VIRTUAL_PHYSICAL_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_VIRTUAL_PHYSICAL; + TempPkt.CpuId = CpuId; + TempPkt.VirtualAddress = VirtualAddress; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(FDP_VIRTUAL_PHYSICAL_PKT_REQ)); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)PhysicalAddress); //TODO: return success/fail ! + } + UnlockSHM(pFDP->pSharedFDPSHM); + return true; +} + +FDP_EXPORTED +bool FDP_GetState(FDP_SHM* pFDP, FDP_State* DebuggeeState) +{ + if (pFDP == NULL) + { + return false; + } + FDP_GET_STATE_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_GET_STATE; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(FDP_GET_STATE_PKT_REQ)); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)DebuggeeState); //TODO: return success/fail ! + } + UnlockSHM(pFDP->pSharedFDPSHM); + return true; +} + +FDP_EXPORTED +bool FDP_WaitForStateChanged(FDP_SHM *pFDP, FDP_State *DebuggeeState) +{ + if (pFDP == NULL) + { + return false; + } + while (true) + { + usleep(0); + if (FDP_GetStateChanged(pFDP) == true) + { + return FDP_GetState(pFDP, DebuggeeState); + } + } + return true; +} + +FDP_EXPORTED +bool FDP_Init(FDP_SHM* pFDP) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = true; + memset(pFDP->pSharedFDPSHM, 0, FDP_SHM_SHARED_SIZE); + return bReturnValue; +} + +FDP_EXPORTED +bool FDP_SingleStep(FDP_SHM* pFDP, uint32_t CpuId) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = false; + uint32_t InputBufferSize = 0; + FDP_SINGLE_STEP_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_SINGLE_STEP; + TempPkt.CpuId = CpuId; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(FDP_SINGLE_STEP_PKT_REQ)); + InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + +uint8_t FDP_Test(FDP_SHM* pFDP) +{ + if (pFDP == NULL) + { + return false; + } + uint8_t DebuggeState; + FDP_GET_STATE_PKT_REQ* tmpPkt = (FDP_GET_STATE_PKT_REQ*)pFDP->OutputBuffer; + tmpPkt->Type = FDPCMD_TEST; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, pFDP->OutputBuffer, sizeof(FDP_GET_STATE_PKT_REQ)); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&DebuggeState); //TODO: return success/fail ! + } + UnlockSHM(pFDP->pSharedFDPSHM); + return DebuggeState; +} + + +FDP_EXPORTED +bool FDP_GetFxState64(FDP_SHM* pFDP, uint32_t CpuId, FDP_XSAVE_FORMAT64_T* pFxState) +{ + if (pFDP == NULL) + { + return false; + } + FDP_GET_STATE_PKT_REQ* TempPkt = (FDP_GET_STATE_PKT_REQ*)pFDP->OutputBuffer; + TempPkt->Type = FDPCMD_GET_FXSTATE; + TempPkt->CpuId = CpuId; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, pFDP->OutputBuffer, sizeof(FDP_GET_STATE_PKT_REQ)); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)pFxState); //TODO: return success/fail ! + } + UnlockSHM(pFDP->pSharedFDPSHM); + return true; +} + +FDP_EXPORTED +bool FDP_SetFxState64(FDP_SHM* pFDP, uint32_t CpuId, FDP_XSAVE_FORMAT64_T* pFxState64) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = false; + FDP_SET_FX_STATE_REQ* TempPkt = (FDP_SET_FX_STATE_REQ*)pFDP->OutputBuffer; + TempPkt->Type = FDPCMD_SET_FXSTATE; + TempPkt->CpuId = CpuId; + memcpy(&TempPkt->FxState64, pFxState64, sizeof(FDP_XSAVE_FORMAT64_T)); + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, pFDP->OutputBuffer, sizeof(FDP_SET_FX_STATE_REQ)); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); //TODO: return success/fail ! + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + + +FDP_EXPORTED +bool FDP_GetPhysicalMemorySize(FDP_SHM* pFDP, uint64_t* PhysicalMemorySize) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = true; + uint32_t InputBufferSize = 0; + FDP_SIMPLE_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_GET_MEMORYSIZE; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(TempPkt)); + InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)PhysicalMemorySize); + } + UnlockSHM(pFDP->pSharedFDPSHM); + //TODO return bool ! + return bReturnValue; +} + + +FDP_EXPORTED +bool FDP_GetCpuCount(FDP_SHM* pFDP, uint32_t* CPUCount) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = true; + uint32_t InputBufferSize = 0; + FDP_SIMPLE_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_GET_CPU_COUNT; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(TempPkt)); + InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)CPUCount); + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + +FDP_EXPORTED +bool FDP_GetCpuState(FDP_SHM* pFDP, uint32_t CpuId, FDP_State* pDebuggeeState) +{ + if (pFDP == NULL) + { + return false; + } + FDP_GET_CPU_STATE_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_GET_CPU_STATE; + TempPkt.CpuId = CpuId; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(FDP_GET_CPU_STATE_PKT_REQ)); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)pDebuggeeState); //TODO: return success/fail ! + } + UnlockSHM(pFDP->pSharedFDPSHM); + return true; +} + +FDP_EXPORTED +bool FDP_Save(FDP_SHM* pFDP) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = false; + FDP_SIMPLE_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_SAVE; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(TempPkt)); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); //TODO: return success/fail ! + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + +FDP_EXPORTED +bool FDP_Restore(FDP_SHM* pFDP) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = false; + FDP_SIMPLE_PKT_REQ TempPkt; + TempPkt.Type = FDPCMD_RESTORE; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, (uint8_t*)&TempPkt, sizeof(TempPkt)); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); //TODO: return success/fail ! + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + +FDP_EXPORTED +bool FDP_GetStateChanged(FDP_SHM* pFDP) +{ + if (pFDP == NULL) + { + return false; + } + bool StateChanged; + //LockSHM(pFDP->pSharedFDPSHM); + ttas_spinlock_lock(&pFDP->pSharedFDPSHM->stateChangedLock); + { + StateChanged = pFDP->pSharedFDPSHM->stateChanged; + pFDP->pSharedFDPSHM->stateChanged = false; + } + //UnlockSHM(pFDP->pSharedFDPSHM); + ttas_spinlock_unlock(&pFDP->pSharedFDPSHM->stateChangedLock); + return StateChanged; +} + +FDP_EXPORTED +void FDP_SetStateChanged(FDP_SHM* pFDP) +{ + if (pFDP == NULL) + { + return; + } + //LockSHM(pFDP->pSharedFDPSHM); + ttas_spinlock_lock(&pFDP->pSharedFDPSHM->stateChangedLock); + { + pFDP->pSharedFDPSHM->stateChanged = true; + } + //UnlockSHM(pFDP->pSharedFDPSHM); + ttas_spinlock_unlock(&pFDP->pSharedFDPSHM->stateChangedLock); + return; +} + +FDP_EXPORTED +bool FDP_InjectInterrupt(FDP_SHM* pFDP, uint32_t CpuId, uint32_t uInterruptionCode, uint32_t uErrorCode, + uint64_t Cr2Value) +{ + if (pFDP == NULL) + { + return false; + } + bool bReturnValue = false; + FDP_INJECT_INTERRUPT_PKT_REQ* tmpPkt = (FDP_INJECT_INTERRUPT_PKT_REQ*)pFDP->OutputBuffer; + tmpPkt->Type = FDPCMD_INJECT_INTERRUPT; + tmpPkt->CpuId = CpuId; + tmpPkt->Cr2Value = Cr2Value; + tmpPkt->ErrorCode = uErrorCode; + tmpPkt->InterruptionCode = uInterruptionCode; + LockSHM(pFDP->pSharedFDPSHM); + { + WriteFDPData(&pFDP->pSharedFDPSHM->ClientToServer, pFDP->OutputBuffer, sizeof(FDP_INJECT_INTERRUPT_PKT_REQ)); + ReadFDPData(&pFDP->pSharedFDPSHM->ServerToClient, (uint8_t*)&bReturnValue); //TODO: return success/fail ! + } + UnlockSHM(pFDP->pSharedFDPSHM); + return bReturnValue; +} + + + +//Server Part +FDP_EXPORTED +bool FDP_ServerLoop(FDP_SHM* pFDP) +{ + if (pFDP == NULL) + { + return false; + } + uint32_t u32InputBufferSize = 0; + uint32_t u32OutputBuffersize = 0; + pFDP->pFdpServer->bIsRunning = true; + while (pFDP->pFdpServer->bIsRunning) + { + bool bStatus = true; + u32InputBufferSize = ReadFDPData(&pFDP->pSharedFDPSHM->ClientToServer, pFDP->InputBuffer); + if (u32InputBufferSize == 0) + { + return false; + } + uint8_t Type = pFDP->InputBuffer[0]; + switch (Type) + { + case FDPCMD_TEST: + { + pFDP->OutputBuffer[0] = 0; //TODO: true ! + u32OutputBuffersize = 1; + break; + } + case FDPCMD_SAVE: + { + pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnSave(pFDP->pFdpServer->pUserHandle); + u32OutputBuffersize = 1; + break; + } + case FDPCMD_RESTORE: + { + pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnRestore(pFDP->pFdpServer->pUserHandle); + u32OutputBuffersize = 1; + break; + } + case FDPCMD_REBOOT: + { + pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnReboot(pFDP->pFdpServer->pUserHandle); + u32OutputBuffersize = 1; + break; + } + case FDPCMD_GET_CPU_COUNT: + { + uint32_t CpuCout; + pFDP->pFdpServer->pfnGetCpuCount(pFDP->pFdpServer->pUserHandle, &CpuCout); + ((uint32_t*)pFDP->OutputBuffer)[0] = CpuCout; + u32OutputBuffersize = sizeof(CpuCout); + break; + } + case FDPCMD_GET_STATE: + { + uint8_t CurrentState; + pFDP->pFdpServer->pfnGetState(pFDP->pFdpServer->pUserHandle, &CurrentState); + pFDP->OutputBuffer[0] = CurrentState; + u32OutputBuffersize = sizeof(CurrentState); + break; + } + case FDPCMD_GET_CPU_STATE: + { + uint8_t CurrentState = 0; + FDP_GET_STATE_PKT_REQ* TempPkt = (FDP_GET_STATE_PKT_REQ*)pFDP->InputBuffer; + pFDP->pFdpServer->pfnGetCpuState(pFDP->pFdpServer->pUserHandle, TempPkt->CpuId, &CurrentState); + pFDP->OutputBuffer[0] = CurrentState; + u32OutputBuffersize = sizeof(CurrentState); + break; + } + case FDPCMD_GET_MEMORYSIZE: + { + uint64_t u64PhysicalMemorySize; + pFDP->pFdpServer->pfnGetMemorySize(pFDP->pFdpServer->pUserHandle, &u64PhysicalMemorySize); + ((uint64_t*)pFDP->OutputBuffer)[0] = u64PhysicalMemorySize; + u32OutputBuffersize = sizeof(u64PhysicalMemorySize); + break; + } + case FDPCMD_UNSET_BP: + { + FDP_CLEAR_BREAKPOINT_PKT_REQ* TempPkt = (FDP_CLEAR_BREAKPOINT_PKT_REQ*)pFDP->InputBuffer; + pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnUnsetBreakpoint(pFDP->pFdpServer->pUserHandle, TempPkt->BreakpointId); + u32OutputBuffersize = 1; + break; + } + case FDPCMD_SET_BP: + { + FDP_SET_BREAKPOINT_PKT_REQ* TempPkt = (FDP_SET_BREAKPOINT_PKT_REQ*)pFDP->InputBuffer; + ((int*)pFDP->OutputBuffer)[0] = pFDP->pFdpServer->pfnSetBreakpoint(pFDP->pFdpServer->pUserHandle, + TempPkt->CpuId, + TempPkt->BreakpointType, + TempPkt->BreakpointId, + TempPkt->BreakpointAccessType, + TempPkt->BreakpointAddressType, + TempPkt->BreakpointAddress, + TempPkt->BreakpointLength, + TempPkt->BreakpointCr3); + u32OutputBuffersize = sizeof(int); + break; + } + case FDPCMD_VIRTUAL_PHYSICAL: + { + uint64_t PhysicalAddress = 0; + FDP_VIRTUAL_PHYSICAL_PKT_REQ* TempPkt = (FDP_VIRTUAL_PHYSICAL_PKT_REQ*)pFDP->InputBuffer; + pFDP->pFdpServer->pfnVirtualToPhysical(pFDP->pFdpServer->pUserHandle, + TempPkt->CpuId, + TempPkt->VirtualAddress, + &PhysicalAddress); + ((uint64_t*)pFDP->OutputBuffer)[0] = PhysicalAddress; + u32OutputBuffersize = sizeof(PhysicalAddress); + break; + } + case FDPCMD_RESUME_VM: + pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnResume(pFDP->pFdpServer->pUserHandle); + u32OutputBuffersize = sizeof(bool); + break; + case FDPCMD_PAUSE_VM: + pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnPause(pFDP->pFdpServer->pUserHandle); + u32OutputBuffersize = sizeof(bool); + break; + case FDPCMD_SINGLE_STEP: + { + FDP_GET_STATE_PKT_REQ* TempPkt = (FDP_GET_STATE_PKT_REQ*)pFDP->InputBuffer; + pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnSingleStep(pFDP->pFdpServer->pUserHandle, TempPkt->CpuId); + u32OutputBuffersize = sizeof(bool); + break; + } + case FDPCMD_READ_REGISTER: + { + uint64_t RegisterValue = 0; + FDP_READ_REGISTER_PKT_REQ* TempPkt = (FDP_READ_REGISTER_PKT_REQ*)pFDP->InputBuffer; + pFDP->pFdpServer->pfnReadRegister(pFDP->pFdpServer->pUserHandle, + TempPkt->CpuId, + TempPkt->RegisterId, + &RegisterValue); + ((uint64_t*)pFDP->OutputBuffer)[0] = RegisterValue; + u32OutputBuffersize = sizeof(RegisterValue); + break; + } + case FDPCMD_GET_FXSTATE: + { + FDP_GET_STATE_PKT_REQ* TempPkt = (FDP_GET_STATE_PKT_REQ*)pFDP->InputBuffer; + pFDP->pFdpServer->pfnGetFxState64(pFDP->pFdpServer->pUserHandle, + TempPkt->CpuId, + pFDP->OutputBuffer, + &u32OutputBuffersize); + break; + } + case FDPCMD_SET_FXSTATE: + { + FDP_SET_FX_STATE_REQ* TempPkt = (FDP_SET_FX_STATE_REQ*)pFDP->InputBuffer; + pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnSetFxState64(pFDP->pFdpServer->pUserHandle, + TempPkt->CpuId, + (uint8_t*)&TempPkt->FxState64, + sizeof(FDP_XSAVE_FORMAT64_T)); + u32OutputBuffersize = sizeof(bool); + break; + } + case FDPCMD_READ_MSR: + { + uint64_t MsrValue = 0; + FDP_READ_MSR_PKT_REQ* TempPkt = (FDP_READ_MSR_PKT_REQ*)pFDP->InputBuffer; + pFDP->pFdpServer->pfnReadMsr(pFDP->pFdpServer->pUserHandle, + TempPkt->CpuId, + TempPkt->MsrId, + &MsrValue); + ((uint64_t*)pFDP->OutputBuffer)[0] = MsrValue; + u32OutputBuffersize = sizeof(uint64_t); + break; + } + case FDPCMD_WRITE_MSR: + { + FDP_WRITE_MSR_PKT_REQ* TempPkt = (FDP_WRITE_MSR_PKT_REQ*)pFDP->InputBuffer; + pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnWriteMsr(pFDP->pFdpServer->pUserHandle, + TempPkt->CpuId, + TempPkt->MsrId, + TempPkt->MsrValue); + u32OutputBuffersize = sizeof(bool); + break; + } + case FDPCMD_WRITE_REGISTER: + { + FDP_WRITE_REGISTER_PKT_REQ* TempPkt = (FDP_WRITE_REGISTER_PKT_REQ*)pFDP->InputBuffer; + pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnWriteRegister(pFDP->pFdpServer->pUserHandle, + TempPkt->CpuId, + TempPkt->RegisterId, + TempPkt->RegisterValue); + u32OutputBuffersize = sizeof(bool); + break; + } + case FDPCMD_READ_PHYSICAL: + { + FDP_READ_PHYSICAL_MEMORY_PKT_REQ* TempPkt = (FDP_READ_PHYSICAL_MEMORY_PKT_REQ*)pFDP->InputBuffer; + if (TempPkt->ReadSize > FDP_MAX_DATA_SIZE) + { + bStatus = false; + } + else + { + bStatus = pFDP->pFdpServer->pfnReadPhysicalMemory(pFDP->pFdpServer->pUserHandle, + pFDP->OutputBuffer, + TempPkt->PhysicalAddress, + TempPkt->ReadSize); + } + if (bStatus) + { + u32OutputBuffersize = TempPkt->ReadSize; + } + else + { + u32OutputBuffersize = 1; + } + break; + } + case FDPCMD_READ_VIRTUAL: + { + FDP_READ_VIRTUAL_MEMORY_PKT_REQ* TempPkt = (FDP_READ_VIRTUAL_MEMORY_PKT_REQ*)pFDP->InputBuffer; + if (TempPkt->ReadSize > FDP_MAX_DATA_SIZE) + { + bStatus = false; + } + else + { + bStatus = pFDP->pFdpServer->pfnReadVirtualMemory(pFDP->pFdpServer->pUserHandle, + TempPkt->CpuId, + TempPkt->VirtualAddress, + TempPkt->ReadSize, + pFDP->OutputBuffer); + } + if (bStatus) + { + u32OutputBuffersize = TempPkt->ReadSize; + } + else + { + u32OutputBuffersize = 1; + } + break; + } + case FDPCMD_WRITE_PHYSICAL: + { + FDP_WRITE_PHYSICAL_MEMORY_PKT_REQ* TempPkt = (FDP_WRITE_PHYSICAL_MEMORY_PKT_REQ*)pFDP->InputBuffer; + pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnWritePhysicalMemory(pFDP->pFdpServer->pUserHandle, + TempPkt->Data, + TempPkt->PhysicalAddress, + TempPkt->WriteSize); + u32OutputBuffersize = sizeof(bool); + break; + } + case FDPCMD_WRITE_VIRTUAL: + { + FDP_WRITE_VIRTUAL_MEMORY_PKT_REQ* TempPkt = (FDP_WRITE_VIRTUAL_MEMORY_PKT_REQ*)pFDP->InputBuffer; + pFDP->OutputBuffer[0] = pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnWriteVirtualMemory( + pFDP->pFdpServer->pUserHandle, + TempPkt->CpuId, + TempPkt->Data, + TempPkt->VirtualAddress, + TempPkt->WriteSize); + u32OutputBuffersize = sizeof(bool); + break; + } + case FDPCMD_INJECT_INTERRUPT: + { + FDP_INJECT_INTERRUPT_PKT_REQ* TempPkt = (FDP_INJECT_INTERRUPT_PKT_REQ*)pFDP->InputBuffer; + pFDP->OutputBuffer[0] = pFDP->OutputBuffer[0] = pFDP->pFdpServer->pfnInjectInterrupt( + pFDP->pFdpServer->pUserHandle, + TempPkt->CpuId, + TempPkt->InterruptionCode, + TempPkt->ErrorCode, + TempPkt->Cr2Value + ); + u32OutputBuffersize = sizeof(bool); + break; + } + //TODO ! + case FDPCMD_SEARCH_PHYSICAL_MEMORY: + { + /*FDP_SEARCH_PHYSICAL_MEMORY_PKT_REQ* tmpPkt = (FDP_SEARCH_PHYSICAL_MEMORY_PKT_REQ*)pFDP->InputBuffer; + + ((uint64_t*)pFDP->OutputBuffer)[0] = pFDP->pFdpServer->pfnSear + + ((uint64_t*)myFDPHandle.OutputBuffer)[0] = -1; + if (tmpPkt->StartOffset < MMR3PhysGetRamSizeU(pUVM)){ + int rc = PGMR3DbgScanPhysicalU(pUVM, tmpPkt->StartOffset, MMR3PhysGetRamSizeU(pUVM) - tmpPkt->StartOffset, 1, tmpPkt->PatternData, tmpPkt->PatternSize, &HitAddress); + ((uint64_t*)myFDPHandle.OutputBuffer)[0] = HitAddress; + if (RT_FAILURE(rc)){ + ((uint64_t*)myFDPHandle.OutputBuffer)[0] = -1; + + } + + } + myFDPHandle.OutputBufferSize = sizeof(uint64_t); + */ + break; + } + default: + break; + } + //There is something to send ! + if (u32OutputBuffersize > 0) + { + WriteFDPDataWithStatus(&pFDP->pSharedFDPSHM->ServerToClient, pFDP->OutputBuffer, u32OutputBuffersize, bStatus); + u32OutputBuffersize = 0; + } + } + return true; +} + +FDP_EXPORTED +bool FDP_SetFDPServer(FDP_SHM* pFDP, FDP_SERVER_INTERFACE_T* pFDPServer) +{ + if (pFDP == NULL) + { + return false; + } + pFDP->pFdpServer = pFDPServer; + return true; +} diff --git a/FDPutils/FDP/include/FDP.h b/FDPutils/FDP/include/FDP.h new file mode 100644 index 00000000..a491fadc --- /dev/null +++ b/FDPutils/FDP/include/FDP.h @@ -0,0 +1,153 @@ +/* + MIT License + + Copyright (c) 2015 Nicolas Couffin ncouffin@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef __FDP_H__ +#define __FDP_H__ + +typedef void* LPVOID; +typedef unsigned long DWORD; +#define WINAPI +#define _In_ + +#ifndef uint128_t +typedef unsigned __int128 uint128_t; +#endif + +#include + +#include "FDP_enum.h" + + +#ifndef FDP_EXPORTED +#define FDP_EXPORTED +#endif + +#define FDP_NO_CR3 0 + +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct __attribute((aligned(16))) FDP_XSAVE_FORMAT64_T_ + { + uint16_t ControlWord; + uint16_t StatusWord; + uint8_t TagWord; + uint8_t Reserved1; + uint16_t ErrorOpcode; + uint32_t ErrorOffset; + uint16_t ErrorSelector; + uint16_t Reserved2; + uint32_t DataOffset; + uint16_t DataSelector; + uint16_t Reserved3; + uint32_t MxCsr; + uint32_t MxCsr_Mask; + uint128_t FloatRegisters[8]; + + uint128_t XmmRegisters[16]; + uint8_t Reserved4[96]; + } FDP_XSAVE_FORMAT64_T; + +#define FDP_MAX_BREAKPOINT 255 + + + typedef __attribute((aligned(1))) struct FDP_SHM_ FDP_SHM; + + typedef struct _FDP_SERVER_INTERFACE_T{ + bool bIsRunning; + + void *pUserHandle; + + //all function ptr! + bool(*pfnGetState) (void *, uint8_t*); + bool(*pfnReadRegister) (void *, uint32_t, FDP_Register, uint64_t*); + bool(*pfnWriteRegister) (void *, uint32_t, FDP_Register, uint64_t); + bool(*pfnWritePhysicalMemory) (void*, uint8_t*, uint64_t, uint32_t); + bool(*pfnReadPhysicalMemory) (void*, uint8_t*, uint64_t, uint32_t); + bool(*pfnWriteVirtualMemory) (void*, uint32_t, uint8_t*, uint64_t, uint32_t); + bool(*pfnGetMemorySize) (void *, uint64_t *); + bool(*pfnResume) (void*); + bool(*pfnPause) (void*); + bool(*pfnSingleStep) (void*, uint32_t); + bool(*pfnWriteMsr) (void*, uint32_t, uint64_t, uint64_t); + bool(*pfnReadMsr) (void*, uint32_t, uint64_t, uint64_t*); + bool(*pfnGetCpuCount) (void*, uint32_t*); + bool(*pfnGetCpuState) (void*, uint32_t, uint8_t*); + bool(*pfnUnsetBreakpoint) (void*, uint8_t); + bool(*pfnVirtualToPhysical) (void*, uint32_t, uint64_t, uint64_t*); + bool(*pfnGetFxState64) (void*, uint32_t, uint8_t*, uint32_t*); + bool(*pfnSetFxState64) (void*, uint32_t, uint8_t*, uint32_t); + bool(*pfnReadVirtualMemory) (void*, uint32_t, uint64_t, uint32_t, uint8_t*); + int(*pfnSetBreakpoint) (void *, uint32_t, FDP_BreakpointType, uint8_t, FDP_Access, FDP_AddressType, uint64_t, uint64_t, uint64_t); + bool(*pfnSave) (void*); + bool(*pfnRestore) (void*); + bool(*pfnReboot) (void*); + bool(*pfnInjectInterrupt) (void*, uint32_t, uint32_t, uint32_t, uint64_t); + }FDP_SERVER_INTERFACE_T; + + // FDP API +FDP_EXPORTED FDP_SHM* FDP_CreateSHM(char *shmName); +FDP_EXPORTED FDP_SHM* FDP_OpenSHM(const char *pShmName); +FDP_EXPORTED bool FDP_Init(FDP_SHM *pShm); +FDP_EXPORTED bool FDP_Pause(FDP_SHM *pShm); +FDP_EXPORTED bool FDP_Resume(FDP_SHM *pShm); +FDP_EXPORTED bool FDP_ReadPhysicalMemory(FDP_SHM *pShm, uint8_t *pDstBuffer, uint32_t ReadSize, uint64_t PhysicalAddress); +FDP_EXPORTED bool FDP_WritePhysicalMemory(FDP_SHM *pShm, uint8_t *pSrcBuffer, uint32_t WriteSize, uint64_t PhysicalAddress); +FDP_EXPORTED bool FDP_ReadVirtualMemory(FDP_SHM *pShm, uint32_t CpuId, uint8_t *pDstBuffer, uint32_t ReadSize, uint64_t VirtualAddress); +FDP_EXPORTED bool FDP_WriteVirtualMemory(FDP_SHM *pShm, uint32_t CpuId, uint8_t *pSrcBuffer, uint32_t WriteSize, uint64_t VirtualAddress); +FDP_EXPORTED uint64_t FDP_SearchPhysicalMemory(FDP_SHM *pShm, const void *pPatternData, uint32_t PatternSize, uint64_t StartOffset); +FDP_EXPORTED bool FDP_SearchVirtualMemory(FDP_SHM *pFDP, uint32_t CpuId, const void *pPatternData, uint32_t PatternSize, uint64_t StartOffset); +FDP_EXPORTED bool FDP_ReadRegister(FDP_SHM *pShm, uint32_t CpuId, FDP_Register RegisterId, uint64_t *pRegisterValue); +FDP_EXPORTED bool FDP_WriteRegister(FDP_SHM *pShm, uint32_t CpuId, FDP_Register RegisterId, uint64_t RegisterValue); +FDP_EXPORTED bool FDP_ReadMsr(FDP_SHM *pShm, uint32_t CpuId, uint64_t MsrId, uint64_t *pMsrValue); +FDP_EXPORTED bool FDP_WriteMsr(FDP_SHM *pShm, uint32_t CpuId, uint64_t MsrId, uint64_t MsrValue); +FDP_EXPORTED int FDP_SetBreakpoint(FDP_SHM *pShm, uint32_t CpuId, FDP_BreakpointType BreakpointType, uint8_t BreakpointId, FDP_Access BreakpointAccessType, FDP_AddressType BreakpointAddressType, uint64_t BreakpointAddress, uint64_t BreakpointLength, uint64_t BreakpointCr3); +FDP_EXPORTED bool FDP_UnsetBreakpoint(FDP_SHM *pShm, uint8_t BreakpointId); +FDP_EXPORTED bool FDP_VirtualToPhysical(FDP_SHM *pShm, uint32_t CpuId, uint64_t VirtualAddress, uint64_t *pPhysicalAddress); +FDP_EXPORTED bool FDP_GetState(FDP_SHM *pShm, FDP_State *pState); +FDP_EXPORTED bool FDP_GetFxState64(FDP_SHM *pShm, uint32_t CpuId, FDP_XSAVE_FORMAT64_T *pFxState64); +FDP_EXPORTED bool FDP_SetFxState64(FDP_SHM *pFDP, uint32_t CpuId, FDP_XSAVE_FORMAT64_T* pFxState64); +FDP_EXPORTED bool FDP_SingleStep(FDP_SHM *pShm, uint32_t CpuId); +FDP_EXPORTED bool FDP_GetPhysicalMemorySize(FDP_SHM *pShm, uint64_t *pPhysicalMemorySize); +FDP_EXPORTED bool FDP_GetCpuCount(FDP_SHM *pShm, uint32_t *pCPUCount); +FDP_EXPORTED bool FDP_GetCpuState(FDP_SHM *pShm, uint32_t CpuId, FDP_State *pState); +FDP_EXPORTED bool FDP_Reboot(FDP_SHM *pShm); +FDP_EXPORTED bool FDP_Save(FDP_SHM *pShm); +FDP_EXPORTED bool FDP_Restore(FDP_SHM *pShm); +FDP_EXPORTED bool FDP_GetStateChanged(FDP_SHM *pShm); +FDP_EXPORTED void FDP_SetStateChanged(FDP_SHM *pShm); +FDP_EXPORTED bool FDP_InjectInterrupt(FDP_SHM *pShm, uint32_t CpuId, uint32_t uInterruptionCode, uint32_t uErrorCode, uint64_t Cr2Value); + +FDP_EXPORTED bool FDP_SetFDPServer(FDP_SHM* pFDP, FDP_SERVER_INTERFACE_T* pFDPServer); +FDP_EXPORTED bool FDP_ServerLoop(FDP_SHM* pFDP); + + uint8_t FDP_Test(FDP_SHM *pShm); + + bool FDP_UnitTest(); + +#ifdef __cplusplus +} +#endif + +#endif //__FDP_H__ diff --git a/FDPutils/FDP/include/FDP_enum.h b/FDPutils/FDP/include/FDP_enum.h new file mode 100644 index 00000000..952de2ba --- /dev/null +++ b/FDPutils/FDP/include/FDP_enum.h @@ -0,0 +1,126 @@ +/* + MIT License + + Copyright (c) 2015 Nicolas Couffin ncouffin@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef _FDP_ENUM_H_ +#define _FDP_ENUM_H_ + +enum FDP_BreakpointType_ +{ + FDP_INVHBP = 0x0, + FDP_SOFTHBP, + FDP_HARDHBP, + FDP_PAGEHBP, + FDP_MSRHBP, + FDP_CRHBP, + FDP_BREAKPOINT_ID_HACK = 0xFFFF +}; +typedef uint16_t FDP_BreakpointType; + + +enum FDP_AddressType_ +{ + FDP_WRONG_ADDRESS = 0x0, + FDP_VIRTUAL_ADDRESS = 0x01, + FDP_PHYSICAL_ADDRESS = 0x02, + FDP_ADRESSTYPE_HACK = 0xFFFF +}; +typedef uint16_t FDP_AddressType; + +enum FDP_Access_ +{ + FDP_WRONG_BP = 0x0, + FDP_EXECUTE_BP = 0x01, + FDP_WRITE_BP = 0x02, + FDP_READ_BP = 0x04, + FDP_INSTRUCTION_FETCH_BP = 0x08, + FDBP_ACCESS_HACK = 0xFFFF, +}; +typedef uint16_t FDP_Access; + +enum FDP_State_ +{ + FDP_STATE_NULL = 0x0, + FDP_STATE_PAUSED = 0x1, + FDP_STATE_BREAKPOINT_HIT = 0x2, + FDP_STATE_DEBUGGER_ALERTED = 0x4, + FDP_STATE_HARD_BREAKPOINT_HIT = 0x8, + FDP_STATE_HACK = 0xFFFF +}; +typedef uint16_t FDP_State; + +enum FDP_Register_ { + FDP_RAX_REGISTER = 0x0, + FDP_RBX_REGISTER, + FDP_RCX_REGISTER, + FDP_RDX_REGISTER, + FDP_R8_REGISTER, + FDP_R9_REGISTER, + FDP_R10_REGISTER, + FDP_R11_REGISTER, + FDP_R12_REGISTER, + FDP_R13_REGISTER, + FDP_R14_REGISTER, + FDP_R15_REGISTER, + FDP_RSP_REGISTER, + FDP_RBP_REGISTER, + FDP_RSI_REGISTER, + FDP_RDI_REGISTER, + FDP_RIP_REGISTER, + FDP_DR0_REGISTER, + FDP_DR1_REGISTER, + FDP_DR2_REGISTER, + FDP_DR3_REGISTER, + FDP_DR6_REGISTER, + FDP_DR7_REGISTER, + FDP_VDR0_REGISTER, + FDP_VDR1_REGISTER, + FDP_VDR2_REGISTER, + FDP_VDR3_REGISTER, + FDP_VDR6_REGISTER, + FDP_VDR7_REGISTER, + FDP_CS_REGISTER, + FDP_DS_REGISTER, + FDP_ES_REGISTER, + FDP_FS_REGISTER, + FDP_GS_REGISTER, + FDP_SS_REGISTER, + FDP_RFLAGS_REGISTER, + FDP_MXCSR_REGISTER, + FDP_GDTRB_REGISTER, + FDP_GDTRL_REGISTER, + FDP_IDTRB_REGISTER, + FDP_IDTRL_REGISTER, + FDP_CR0_REGISTER, + FDP_CR2_REGISTER, + FDP_CR3_REGISTER, + FDP_CR4_REGISTER, + FDP_CR8_REGISTER, + FDP_LDTR_REGISTER, + FDP_LDTRB_REGISTER, + FDP_LDTRL_REGISTER, + FDP_TR_REGISTER, + FDP_REGISTER_UINT16_TRICK = 0xFFFF +}; +typedef uint16_t FDP_Register; + +#endif diff --git a/FDPutils/FDP/include/FDP_structs.h b/FDPutils/FDP/include/FDP_structs.h new file mode 100644 index 00000000..3a18c9bd --- /dev/null +++ b/FDPutils/FDP/include/FDP_structs.h @@ -0,0 +1,308 @@ +/* + MIT License + + Copyright (c) 2015 Nicolas Couffin ncouffin@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef FDP_STRUCTS_H__ +#define FDP_STRUCTS_H__ + +//#include +//#include + +#pragma pack(push, 1) +typedef struct FDP_CPU_CTX_ +{ + uint64_t rip; + + uint64_t rax; + uint64_t rcx; + uint64_t rdx; + uint64_t rbx; + + uint64_t rsp; + uint64_t rbp; + uint64_t rsi; + uint64_t rdi; + uint64_t r8; + uint64_t r9; + uint64_t r10; + uint64_t r11; + uint64_t r12; + uint64_t r13; + uint64_t r14; + uint64_t r15; + + uint64_t es; + uint64_t cs; + uint64_t ss; + uint64_t ds; + uint64_t fs; + uint64_t gs; + + uint64_t rflags; + + uint64_t cr0; + uint64_t cr2; + uint64_t cr3; + uint64_t cr4; +}FDP_CPU_CTX; +#pragma pack(pop) + + +enum +{ + FDPCMD_INIT, + FDPCMD_PHYSICAL_VIRTUAL, + FDPCMD_READ_PHYSICAL, + FDPCMD_READ_REGISTER, + FDPCMD_READ_MSR, + FDPCMD_WRITE_MSR, + FDPCMD_GET_MEMORYSIZE, + FDPCMD_PAUSE_VM, + FDPCMD_RESUME_VM, + FDPCMD_SEARCH_PHYSICAL_MEMORY, + FDPCMD_SEARCH_VIRTUAL_MEMORY, + FDPCMD_UNSET_BP, + FDPCMD_SET_BP, + FDPCMD_VIRTUAL_PHYSICAL, + FDPCMD_WRITE_PHYSICAL, + FDPCMD_WRITE_VIRTUAL, + FDPCMD_GET_STATE, + FDPCMD_READ_VIRTUAL, + FDPCMD_WRITE_REGISTER, + FDPCMD_GET_FXSTATE, + FDPCMD_SET_FXSTATE, + FDPCMD_SINGLE_STEP, + FDPCMD_GET_CPU_COUNT, + FDPCMD_GET_CPU_STATE, + FDPCMD_GET_CURRENT_CPU, + FDPCMD_SWITCH_CPU, + FDPCMD_REBOOT, + FDPCMD_SAVE, + FDPCMD_RESTORE, + FDPCMD_INJECT_INTERRUPT, + FDPCMD_TEST +}; + +typedef struct _FDP_UnsetBreakpoint_req +{ + uint8_t cmdType; + uint8_t breakPointId; +} FDP_UnsetBreakpoint_req; + +typedef struct _FDP_SetBreakpoint_req +{ + uint8_t cmdType; + uint8_t breakPointId; + uint64_t breakAddress; +} FDP_SetBreakpoint_req; + +#pragma pack(push, 1) +#pragma warning( disable : 4200 ) + +#define FDP_1M 1024*1024 +#define FDP_MAX_DATA_SIZE 10*FDP_1M + +typedef __attribute__((aligned(1))) struct FDP_SHM_CANAL_ +{ + volatile bool lock; //Per channel lock + volatile bool bDataPresent; //is data present + volatile bool bStatus; + volatile uint32_t dataSize; + volatile uint8_t data[FDP_MAX_DATA_SIZE]; +} FDP_SHM_CANAL; + +typedef __attribute__((aligned(1))) struct FDP_SHM_SHARED_ +{ + volatile bool lock; //General lock for the whole FDP_SHM_SHARED + volatile bool stateChangedLock; + volatile bool stateChanged; + FDP_SHM_CANAL ClientToServer; + FDP_SHM_CANAL ServerToClient; +} FDP_SHM_SHARED; + +typedef __attribute__((aligned(1))) struct FDP_SHM_ +{ + FDP_SHM_SHARED *pSharedFDPSHM; //Shared part of the FDP SHM + uint8_t InputBuffer[FDP_MAX_DATA_SIZE]; //Used as temporary input buffer + uint8_t OutputBuffer[FDP_MAX_DATA_SIZE]; //Used as temporary output buffer + + FDP_SERVER_INTERFACE_T *pFdpServer; + FDP_CPU_CTX *pCpuShm; +} FDP_SHM; + +#define FDP_SHM_SHARED_SIZE sizeof(FDP_SHM_SHARED) + + +typedef struct FDP_SIMPLE_PKT_REQ_ +{ + uint8_t Type; +} FDP_SIMPLE_PKT_REQ; + +typedef struct FDP_READ_PHYSICAL_MEMORY_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; + uint64_t PhysicalAddress; + uint32_t ReadSize; +} FDP_READ_PHYSICAL_MEMORY_PKT_REQ; + +typedef struct FDP_READ_VIRTUAL_MEMORY_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; + uint64_t VirtualAddress; + uint32_t ReadSize; +} FDP_READ_VIRTUAL_MEMORY_PKT_REQ; + +typedef struct FDP_WRITE_PHYSICAL_MEMORY_PKT_REQ_ +{ + uint8_t Type; + uint64_t PhysicalAddress; + uint32_t WriteSize; + uint8_t Data[]; +} FDP_WRITE_PHYSICAL_MEMORY_PKT_REQ; + +typedef struct FDP_WRITE_VIRTUAL_MEMORY_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; + uint64_t VirtualAddress; + uint32_t WriteSize; + uint8_t Data[]; +} FDP_WRITE_VIRTUAL_MEMORY_PKT_REQ; + +typedef struct FDP_SEARCH_PHYSICAL_MEMORY_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; + uint32_t PatternSize; + uint64_t StartOffset; + uint8_t PatternData[]; +} FDP_SEARCH_PHYSICAL_MEMORY_PKT_REQ; + +typedef struct FDP_SEARCH_VIRTUAL_MEMORY_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; + uint32_t PatternSize; + uint64_t StartOffset; + uint8_t PatternData[]; +} FDP_SEARCH_VIRTUAL_MEMORY_PKT_REQ; + +typedef struct FDP_READ_REGISTER_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; + FDP_Register RegisterId; +} FDP_READ_REGISTER_PKT_REQ; + +typedef struct FDP_READ_MSR_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; + uint64_t MsrId; +} FDP_READ_MSR_PKT_REQ; + +typedef struct FDP_WRITE_MSR_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; + uint64_t MsrId; + uint64_t MsrValue; +} FDP_WRITE_MSR_PKT_REQ; + +typedef struct FDP_WRITE_REGISTER_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; + FDP_Register RegisterId; + uint64_t RegisterValue; +} FDP_WRITE_REGISTER_PKT_REQ; + +typedef struct FDP_VIRTUAL_PHYSICAL_PKT_REQ +{ + uint8_t Type; + uint32_t CpuId; + uint64_t VirtualAddress; +} FDP_VIRTUAL_PHYSICAL_PKT_REQ; + +typedef struct FDP_GET_STATE_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; +} FDP_GET_STATE_PKT_REQ; + +typedef struct FDP_SINGLE_STEP_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; +} FDP_SINGLE_STEP_PKT_REQ; + +typedef struct FDP_GET_CPU_STATE_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; +} FDP_GET_CPU_STATE_PKT_REQ; + +typedef struct FDP_UNSET_BREAKPOINT_PKT_REQ +{ + uint8_t Type; + uint8_t BreakpointId; +} FDP_CLEAR_BREAKPOINT_PKT_REQ; + +/*typedef struct FDP_SWITCH_CPU_PKT_REQ_ +{ +uint8_t Type; +uint32_t CPUIndex; +} FDP_SWITCH_CPU_PKT_REQ;*/ + +typedef struct FDP_SET_BREAKPOINT_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; + FDP_BreakpointType BreakpointType; + uint8_t BreakpointId; + FDP_Access BreakpointAccessType; + FDP_AddressType BreakpointAddressType; + uint64_t BreakpointAddress; + uint64_t BreakpointLength; + uint64_t BreakpointCr3; +} FDP_SET_BREAKPOINT_PKT_REQ; + +typedef struct FDP_INJECT_INTERRUPT_PKT_REQ_ +{ + uint8_t Type; + uint32_t CpuId; + uint32_t InterruptionCode; + uint32_t ErrorCode; + uint64_t Cr2Value; +} FDP_INJECT_INTERRUPT_PKT_REQ; + +typedef struct FDP_SET_FX_STATE_REQ_ +{ + uint8_t Type; + uint32_t CpuId; + FDP_XSAVE_FORMAT64_T FxState64; +}FDP_SET_FX_STATE_REQ; +#pragma pack(pop) + +#endif diff --git a/FDPutils/PyFDP/PyFDP/FDP.py b/FDPutils/PyFDP/PyFDP/FDP.py new file mode 100644 index 00000000..da7963fd --- /dev/null +++ b/FDPutils/PyFDP/PyFDP/FDP.py @@ -0,0 +1,402 @@ +from ctypes import * +import os +import re + +import PyFDP + +FDP_REGISTER = [ + {'name': "FDP_RAX_REGISTER" ,'value': 0x0}, + {'name': "FDP_RBX_REGISTER" ,'value': 0x1}, + {'name': "FDP_RCX_REGISTER" ,'value': 0x2}, + {'name': "FDP_RDX_REGISTER" ,'value': 0x3}, + {'name': "FDP_R8_REGISTER" ,'value': 0x4}, + {'name': "FDP_R9_REGISTER" ,'value': 0x5}, + {'name': "FDP_R10_REGISTER" ,'value': 0x6}, + {'name': "FDP_R11_REGISTER" ,'value': 0x7}, + {'name': "FDP_R12_REGISTER" ,'value': 0x8}, + {'name': "FDP_R13_REGISTER" ,'value': 0x9}, + {'name': "FDP_R14_REGISTER" ,'value': 0xa}, + {'name': "FDP_R15_REGISTER" ,'value': 0xb}, + {'name': "FDP_RSP_REGISTER" ,'value': 0xc}, + {'name': "FDP_RBP_REGISTER" ,'value': 0xd}, + {'name': "FDP_RSI_REGISTER" ,'value': 0xe}, + {'name': "FDP_RDI_REGISTER" ,'value': 0xf}, + {'name': "FDP_RIP_REGISTER" ,'value': 0x10}, + {'name': "FDP_DR0_REGISTER" ,'value': 0x11}, + {'name': "FDP_DR1_REGISTER" ,'value': 0x12}, + {'name': "FDP_DR2_REGISTER" ,'value': 0x13}, + {'name': "FDP_DR3_REGISTER" ,'value': 0x14}, + {'name': "FDP_DR6_REGISTER" ,'value': 0x15}, + {'name': "FDP_DR7_REGISTER" ,'value': 0x16}, + {'name': "FDP_VDR0_REGISTER" ,'value': 0x17}, + {'name': "FDP_VDR1_REGISTER" ,'value': 0x18}, + {'name': "FDP_VDR2_REGISTER" ,'value': 0x19}, + {'name': "FDP_VDR3_REGISTER" ,'value': 0x1a}, + {'name': "FDP_VDR6_REGISTER" ,'value': 0x1b}, + {'name': "FDP_VDR7_REGISTER" ,'value': 0x1c}, + {'name': "FDP_CS_REGISTER" ,'value': 0x1d}, + {'name': "FDP_DS_REGISTER" ,'value': 0x1e}, + {'name': "FDP_ES_REGISTER" ,'value': 0x1f}, + {'name': "FDP_FS_REGISTER" ,'value': 0x20}, + {'name': "FDP_GS_REGISTER" ,'value': 0x21}, + {'name': "FDP_SS_REGISTER" ,'value': 0x22}, + {'name': "FDP_RFLAGS_REGISTER" ,'value': 0x23}, + {'name': "FDP_MXCSR_REGISTER" ,'value': 0x24}, + {'name': "FDP_GDTRB_REGISTER" ,'value': 0x25}, + {'name': "FDP_GDTRL_REGISTER" ,'value': 0x26}, + {'name': "FDP_IDTRB_REGISTER" ,'value': 0x27}, + {'name': "FDP_IDTRL_REGISTER" ,'value': 0x28}, + {'name': "FDP_CR0_REGISTER" ,'value': 0x29}, + {'name': "FDP_CR2_REGISTER" ,'value': 0x2a}, + {'name': "FDP_CR3_REGISTER" ,'value': 0x2b}, + {'name': "FDP_CR4_REGISTER" ,'value': 0x2c}, + {'name': "FDP_CR8_REGISTER" ,'value': 0x2d} +] + +class FDP(object): + """ Fast Debug Protocol client object. + + Send requests to a FDP server located in an instrumented VirtualBox implementation. + """ + + FDP_MAX_BREAKPOINT = 256 + + FDP_NO_CR3 = 0x0 + + # FDP_BreakpointType + FDP_SOFTHBP = 0x1 + FDP_HARDHBP = 0x2 + FDP_PAGEHBP = 0x3 + FDP_MSRHBP = 0x4 + FDP_CRHBP = 0x5 + + # FDP_AddressType + FDP_WRONG_ADDRESS = 0x0 + FDP_VIRTUAL_ADDRESS = 0x01 + FDP_PHYSICAL_ADDRESS = 0x02 + + # FDP_Access + FDP_WRONG_BP = 0x00 + FDP_EXECUTE_BP = 0x01 + FDP_WRITE_BP = 0x02 + FDP_READ_BP = 0x04 + FDP_INSTRUCTION_FETCH_BP = 0x08 + + # FDP_State + FDP_STATE_PAUSED = 0x1 + FDP_STATE_BREAKPOINT_HIT = 0x2 + FDP_STATE_DEBUGGER_ALERTED = 0x4 + FDP_STATE_HARD_BREAKPOINT_HIT = 0x8 + + FDP_CPU0 = 0 + + def __init__(self, Name): + + self.pFDP = 0 + self.fdpdll = PyFDP.FDP_DLL_HANDLE + + FDP_Register = c_uint32 + FDP_BreakpointType = c_uint16 + FDP_Access = c_uint16 + FDP_AddressType = c_uint16 + FDP_State = c_uint8 + + self.pRegisterValue = pointer(c_uint64(0)) + self.pMsrValue = pointer(c_uint64(0)) + self.pState = pointer(c_uint8(0)) + + self.fdpdll.FDP_CreateSHM.restype = c_void_p + self.fdpdll.FDP_CreateSHM.argtypes = [c_char_p] + self.fdpdll.FDP_OpenSHM.restype = c_void_p + self.fdpdll.FDP_OpenSHM.argtypes = [c_char_p] + self.fdpdll.FDP_Init.restype = c_bool + self.fdpdll.FDP_Init.argtypes = [c_void_p] + self.fdpdll.FDP_Pause.restype = c_bool + self.fdpdll.FDP_Pause.argtypes = [c_void_p] + self.fdpdll.FDP_Resume.restype = c_bool + self.fdpdll.FDP_Resume.argtypes = [c_void_p] + self.fdpdll.FDP_ReadPhysicalMemory.restype = c_bool + self.fdpdll.FDP_ReadPhysicalMemory.argtypes = [c_void_p, POINTER(c_uint8), c_uint32, c_uint64] + self.fdpdll.FDP_WritePhysicalMemory.restype = c_bool + self.fdpdll.FDP_WritePhysicalMemory.argtypes = [c_void_p, POINTER(c_uint8), c_uint32, c_uint64] + self.fdpdll.FDP_ReadVirtualMemory.restype = c_bool + self.fdpdll.FDP_ReadVirtualMemory.argtypes = [c_void_p, c_uint32, POINTER(c_uint8), c_uint32, c_uint64] + self.fdpdll.FDP_WriteVirtualMemory.restype = c_bool + self.fdpdll.FDP_WriteVirtualMemory.argtypes = [c_void_p, c_uint32, POINTER(c_uint8), c_uint32, c_uint64] + self.fdpdll.FDP_SearchPhysicalMemory.restype = c_uint64 + self.fdpdll.FDP_SearchPhysicalMemory.argtypes = [c_void_p, c_void_p, c_uint32, c_uint64] + self.fdpdll.FDP_SearchVirtualMemory.restype = c_bool + self.fdpdll.FDP_SearchVirtualMemory.argtypes = [c_void_p, c_uint32, c_void_p, c_uint32, c_uint64] + self.fdpdll.FDP_ReadRegister.restype = c_bool + self.fdpdll.FDP_ReadRegister.argtypes = [c_void_p, c_uint32, FDP_Register, POINTER(c_uint64)] + self.fdpdll.FDP_WriteRegister.restype = c_bool + self.fdpdll.FDP_WriteRegister.argtypes = [c_void_p, c_uint32, FDP_Register, c_uint64] + self.fdpdll.FDP_ReadMsr.restype = c_bool + self.fdpdll.FDP_ReadMsr.argtypes = [c_void_p, c_uint32, c_uint64, POINTER(c_uint64)] + self.fdpdll.FDP_WriteMsr.restype = c_bool + self.fdpdll.FDP_WriteMsr.argtypes = [c_void_p, c_uint32, c_uint64, c_uint64] + self.fdpdll.FDP_SetBreakpoint.restype = c_int + self.fdpdll.FDP_SetBreakpoint.argtypes = [c_void_p, c_uint32, FDP_BreakpointType, c_uint8, FDP_Access, FDP_AddressType, c_uint64, c_uint64, c_uint64] + self.fdpdll.FDP_UnsetBreakpoint.restype = c_bool + self.fdpdll.FDP_UnsetBreakpoint.argtypes = [c_void_p, c_uint8] + self.fdpdll.FDP_VirtualToPhysical.restype = c_bool + self.fdpdll.FDP_VirtualToPhysical.argtypes = [c_void_p, c_uint32, c_uint64, POINTER(c_uint64)] + self.fdpdll.FDP_GetState.restype = c_bool + self.fdpdll.FDP_GetState.argtypes = [c_void_p, POINTER(FDP_State)] + self.fdpdll.FDP_GetFxState64.restype = c_bool + self.fdpdll.FDP_GetFxState64.argtypes = [c_void_p, c_uint32, c_void_p] + self.fdpdll.FDP_SetFxState64.restype = c_bool + self.fdpdll.FDP_SetFxState64.argtypes = [c_void_p, c_uint32, c_void_p] + self.fdpdll.FDP_SingleStep.restype = c_bool + self.fdpdll.FDP_SingleStep.argtypes = [c_void_p, c_uint32] + self.fdpdll.FDP_GetPhysicalMemorySize.restype = c_bool + self.fdpdll.FDP_GetPhysicalMemorySize.argtypes = [c_void_p, POINTER(c_uint64)] + self.fdpdll.FDP_GetCpuCount.restype = c_bool + self.fdpdll.FDP_GetCpuCount.argtypes = [c_void_p, POINTER(c_uint32)] + self.fdpdll.FDP_GetCpuState.restype = c_bool + self.fdpdll.FDP_GetCpuState.argtypes = [c_void_p, c_uint32, POINTER(FDP_State)] + self.fdpdll.FDP_Reboot.restype = c_bool + self.fdpdll.FDP_Reboot.argtypes = [c_void_p] + self.fdpdll.FDP_Save.restype = c_bool + self.fdpdll.FDP_Save.argtypes = [c_void_p] + self.fdpdll.FDP_Restore.restype = c_bool + self.fdpdll.FDP_Restore.argtypes = [c_void_p] + self.fdpdll.FDP_GetStateChanged.restype = c_bool + self.fdpdll.FDP_GetStateChanged.argtypes = [c_void_p] + self.fdpdll.FDP_SetStateChanged.restype = c_void_p + self.fdpdll.FDP_SetStateChanged.argtypes = [c_void_p] + self.fdpdll.FDP_InjectInterrupt.restype = c_bool + self.fdpdll.FDP_InjectInterrupt.argtypes = [c_void_p, c_uint32, c_uint32, c_uint32, c_uint64] + + pName = cast(pointer(create_string_buffer(Name.encode())), c_char_p) + self.pFDP = self.fdpdll.FDP_OpenSHM(pName) + if self.pFDP == 0 or self.pFDP is None: + raise Exception("SHMError: impossible to connect to {}".format(Name)) + + self.fdpdll.FDP_Init(self.pFDP) + + # create registers attributes + + for reg in FDP_REGISTER: + enum = reg["value"] + name = self.__fix_names__(reg['name']) + def get_property(enum): + def read(self): return self.ReadRegister(enum) + def write(self, value) : self.WriteRegister(enum, value) + return property(read, write) + setattr(FDP, name, get_property(enum)) + + def __fix_names__(self, name): + regx= re.compile(r"FDP_(.*)_REGISTER") + return re.findall(regx, name)[0].lower() + + def ReadRegister(self, RegisterId, CpuId=FDP_CPU0 ): + """ Return the value stored in the specified register + RegisterId must be a member of FDP.FDP_REGISTER + """ + if self.fdpdll.FDP_ReadRegister(self.pFDP, CpuId, RegisterId, self.pRegisterValue) == True: + return self.pRegisterValue[0] + return None + + def WriteRegister(self, RegisterId, RegisterValue, CpuId=FDP_CPU0): + """ Store the given value into the specified register + RegisterId must be a member of FDP.FDP_REGISTER + """ + return self.fdpdll.FDP_WriteRegister(self.pFDP, CpuId, RegisterId, c_uint64(RegisterValue)) + + def ReadMsr(self, MsrId, CpuId=FDP_CPU0): + """ Return the value stored in the Model-specific register (MSR) indexed by MsrId + MSR typically don't have an enum Id since there are vendor specific. + """ + if self.fdpdll.FDP_ReadMsr(self.pFDP, CpuId, c_uint64(MsrId), self.pMsrValue) == True: + return self.pMsrValue[0] + return None + + def WriteMsr(self, MsrId, MsrValue, CpuId=FDP_CPU0): + """ Store the value into the Model-specific register (MSR) indexed by MsrId + MSR typically don't have an enum Id since there are vendor specific. + """ + return self.fdpdll.FDP_WriteMsr(self.pFDP, CpuId, c_uint64(MsrId), c_uint64(MsrValue)) + + def Pause(self): + """ Suspend the target virtual machine """ + return self.fdpdll.FDP_Pause(self.pFDP) + + def Resume(self): + """ Resume the target virtual machine execution """ + return self.fdpdll.FDP_Resume(self.pFDP) + + def Save(self): + """Save the virtual machine state (CPU+memory). + Only one save state allowed. + """ + return self.fdpdll.FDP_Save(self.pFDP) + + def Restore(self): + """ Restore the previously stored virtual machine state (CPU+memory). """ + return self.fdpdll.FDP_Restore(self.pFDP) + + def Reboot(self): + """ Reboot the target virtual machine """ + return self.fdpdll.FDP_Reboot(self.pFDP) + + def SingleStep(self, CpuId=FDP_CPU0): + """ Single step a paused execution """ + return self.fdpdll.FDP_SingleStep(self.pFDP, CpuId) + + def ReadVirtualMemory(self, VirtualAddress, ReadSize, CpuId=FDP_CPU0): + """ Attempt to read a VM virtual memory buffer. + Check CR3 to know which process's memory your're reading + """ + try: + Buffer = create_string_buffer(int(ReadSize)) + except(OverflowError): + return None + + pBuffer = cast(pointer(Buffer), POINTER(c_uint8)) + if self.fdpdll.FDP_ReadVirtualMemory(self.pFDP, CpuId, pBuffer, ReadSize, c_uint64(VirtualAddress)) == True: + return Buffer.raw + return None + + def ReadPhysicalMemory(self, PhysicalAddress, ReadSize): + """ Attempt to read a VM physical memory buffer. """ + Buffer = create_string_buffer(int(ReadSize)) + pBuffer = cast(pointer(Buffer), POINTER(c_uint8)) + if self.fdpdll.FDP_ReadPhysicalMemory(self.pFDP, pBuffer, ReadSize, c_uint64(PhysicalAddress)) == True: + return Buffer.raw + return None + + def WritePhysicalMemory(self, PhysicalAddress, WriteBuffer): + """ Attempt to write a buffer at a VM physical memory address. """ + Buffer = create_string_buffer(int(WriteBuffer)) + pBuffer = pointer(Buffer) + return self.fdpdll.FDP_WritePhysicalMemory(self.pFDP, pBuffer, len(WriteBuffer), c_uint64(PhysicalAddress)) + + def WriteVirtualMemory(self, VirtualAddress, WriteBuffer, CpuId=FDP_CPU0): + """ Attempt to write a buffer at a VM virtual memory address. + Check CR3 to know which process's memory your're writing into. + """ + Buffer = create_string_buffer(WriteBuffer) + pBuffer = cast(pointer(Buffer), POINTER(c_uint8)) + return self.fdpdll.FDP_WriteVirtualMemory(self.pFDP, CpuId, pBuffer, len(WriteBuffer), c_uint64(VirtualAddress)) + + def SetBreakpoint(self, BreakpointType, BreakpointId, BreakpointAccessType, BreakpointAddressType, BreakpointAddress, BreakpointLength, BreakpointCr3, CpuId=FDP_CPU0): + """ Place a breakpoint. + + * BreakpointType : + - FDP.FDP_SOFTHBP : "soft" hyperbreakpoint, backed by a shadow "0xcc" isntruction in the VM physical memory page. + - FDP.FDP_HARDHBP : "hard" hyperbreakpoint, backed by a shadow debug register (only 4) + - FDP.FDP_PAGEHBP : "page" hyperbreakpoint relying on Extended Page Table (EPT) page guard faults. + - FDP.FDP_MSRHBP : "msr" hyperbreakpoint, specifically to read a VM's MSR + - FDP.FDP_CRHBP : "cr" hyperbreakpoint, specifically to read a VM's Context Register + + * BreakpointId: Currently unused + + * BreakpointAccessType: + - FDP.FDP_EXECUTE_BP : break on execution + - FDP.FDP_WRITE_BP : break on write + - FDP.FDP_READ_BP : break on read + - FDP.FDP_INSTRUCTION_FETCH_BP : break when fetching instructions before executing + + * BreakpointAddressType: + - FDP.FDP_VIRTUAL_ADDRESS : VM's virtual addressing + - FDP.FDP_PHYSICAL_ADDRESS : VM's physical addressing + + * BreakpointAddress: address (virtual or physical) to break execution + + * BreakpointLength: Length of the data pointed by BreakpointAddress which trigger the breakpoint (think "ba e 8" style of breakpoint) + + * BreakpointCr3: Filter breakpoint on a specific CR3 value. Mandatory if you want to break on a particular process. + """ + + BreakpointId = self.fdpdll.FDP_SetBreakpoint(self.pFDP, c_uint32(CpuId), c_uint16(BreakpointType), c_uint8(BreakpointId), c_uint16(BreakpointAccessType), c_uint16(BreakpointAddressType), c_uint64(BreakpointAddress), c_uint64(BreakpointLength), c_uint64(BreakpointCr3)) + if BreakpointId >= 0: + return BreakpointId + return None + + def UnsetBreakpoint(self, BreakpointId): + """ Remove the selected breakoint. Return True on success """ + return self.fdpdll.FDP_UnsetBreakpoint(self.pFDP, c_uint8(BreakpointId)) + + def GetState(self): + """ Return the bitfield state of an system execution break (all CPUs considered): + + - FDP.FDP_STATE_PAUSED : the VM is paused. + - FDP.FDP_STATE_BREAKPOINT_HIT : the execution has hit a soft or page breakpoint + - FDP.FDP_STATE_DEBUGGER_ALERTED : the VM is in a debuggable state + - FDP.FDP_STATE_HARD_BREAKPOINT_HIT : the execution has hit a hard breakpoint + """ + if self.fdpdll.FDP_GetState(self.pFDP, self.pState) == True: + return self.pState[0] + return None + + def GetCpuState(self, CpuId=FDP_CPU0): + """ Return the bitfield state of an execution break for the sprecified CpuId: + + - FDP.FDP_STATE_PAUSED : the VM is paused. + - FDP.FDP_STATE_BREAKPOINT_HIT : the execution has hit a soft or page breakpoint + - FDP.FDP_STATE_DEBUGGER_ALERTED : the VM is in a debuggable state + - FDP.FDP_STATE_HARD_BREAKPOINT_HIT : the execution has hit a hard breakpoint + """ + if self.fdpdll.FDP_GetCpuState(self.pFDP, CpuId, self.pState) == True: + return self.pState[0] + return None + + def GetPhysicalMemorySize(self): + """ return the target VM physical memory size, or None on failure """ + pPhysicalMemorySize = pointer(c_uint64(0)) + if self.fdpdll.FDP_GetPhysicalMemorySize(self.pFDP, pPhysicalMemorySize) == True: + return pPhysicalMemorySize[0] + return None + + def GetCpuCount(self): + """ return the target VM CPU count, or None on failure """ + pCpuCount = pointer(c_uint32(0)) + if self.fdpdll.FDP_GetCpuCount(self.pFDP, pCpuCount) == True: + return pCpuCount[0] + return None + + def GetStateChanged(self): + """ check if the VM execution state has changed. Useful on resume.""" + return self.fdpdll.FDP_GetStateChanged(self.pFDP) + + def WaitForStateChanged(self): + """ wait for the VM execution state has change. Useful on when waiting for a breakpoint to hit.""" + if self.fdpdll.FDP_WaitForStateChanged(self.pFDP, self.pState) == True: + return self.pState[0] + return None + + def InjectInterrupt(self, InterruptionCode, ErrorCode, Cr2Value, CpuId=FDP_CPU0): + """ Inject an interruption in the VM execution state. + + * InterruptionCode (int) : interrupt code (e.g. 0x0E for a #PF) + * ErrorCode : the error code for the interruption (e.g. 0x02 for a Write error on a #PF) + * Cr2Value : typically the address associated with the interruption + """ + return self.fdpdll.FDP_InjectInterrupt(self.pFDP, CpuId, InterruptionCode, ErrorCode, c_uint64(Cr2Value)) + + def UnsetAllBreakpoint(self): + """ Remove every set breakpionts """ + for i in range(0,FDP.FDP_MAX_BREAKPOINT): + self.UnsetBreakpoint(i) + return True + + def WaitForStateChanged(self): + """ wait for the VM state to change """ + while True: + if self.GetStateChanged() == True: + return self.GetState() + return 0 + + def DumpPhysicalMemory(self, FilePath): + """ Write the whole VM physicai memory to the host disk. Useful for Volatility-like tools.""" + _4K = 4096 + PhysicalMemorySize = self.GetPhysicalMemorySize() + + with open(FilePath, 'wb') as dumped_memory_file: + for physical_page in range(0, PhysicalMemorySize, _4K): + PageContent = self.ReadPhysicalMemory(physical_page, _4K) + if PageContent == None: + PageContent = b"?"*_4K + + dumped_memory_file.write(PageContent) diff --git a/FDPutils/PyFDP/PyFDP/__init__.py b/FDPutils/PyFDP/PyFDP/__init__.py new file mode 100644 index 00000000..fa831de3 --- /dev/null +++ b/FDPutils/PyFDP/PyFDP/__init__.py @@ -0,0 +1,37 @@ +import os +import sys +import ctypes + + + + + +# get __file__ path +import inspect +if not hasattr(sys.modules[__name__], '__file__'): + __file__ = inspect.getfile(inspect.currentframe()) + + +# Try to call LoadLibrary on the correct FDP dll file +_found = False +FDP_DLL_HANDLE = None +package_dir = os.path.dirname(__file__) +try: + if sys.platform == "darwin": + _FDP_dll_name = "libFDP.dylib" + else: + is_64bits = sys.maxsize > 2**32 + if is_64bits: + _FDP_dll_name = "FDP_x64.dll" + else: + _FDP_dll_name = "FDP_x86.dll" + + _FDP_dll_path = os.path.join(package_dir, _FDP_dll_name) + FDP_DLL_HANDLE = ctypes.cdll.LoadLibrary(_FDP_dll_path) + _found = True +except OSError: + pass + + +if _found == False or FDP_DLL_HANDLE == None: + raise ImportError("ERROR: fail to load the dynamic library %s." % _FDP_dll_path) diff --git a/FDPutils/PyFDP/setup.py b/FDPutils/PyFDP/setup.py new file mode 100755 index 00000000..25d9bb77 --- /dev/null +++ b/FDPutils/PyFDP/setup.py @@ -0,0 +1,130 @@ +import os +import sys +import shutil +import setuptools + +from distutils import log +from distutils.command.build_clib import build_clib + +VERSION_MAJOR = 1 +VERSION_MINOR = 1 +VERSION_VBOXBIN = "5-0-18-6667" +VERSION = "{major:d}.{minor:d}".format( + major = VERSION_MAJOR, + minor = VERSION_MINOR, +) + +BINDING_DIRECTORY = os.path.abspath(os.path.dirname(__file__)) +ROOT_DIRECTORY = os.path.abspath(os.path.join(BINDING_DIRECTORY, "../../")) + +# path to compiled libraries for Windows +PATH_LIB64 = os.path.join(ROOT_DIRECTORY, "out_x64/Release/FDP_x64.dll") +PATH_LIB32 = os.path.join(ROOT_DIRECTORY, "out_x86/Release/FDP_x86.dll") + +FDP_BUILD_SCRIPT = lambda VS_VERSION : os.path.join(ROOT_DIRECTORY, "buildVS%s.bat" % VS_VERSION) +VS_VERSIONS = [ + "2017", + "2015", + "2013" +] + +class PyFDPCustomBuildClib(build_clib): + """ Customized build_clib command. """ + description = "Custom build lib step used to compile the FDP client dll from sources." + + + def run(self): + log.info('running PyFDPCustomBuildClib') + build_clib.run(self) + + def initialize_options(self): + """ Initialize custom command line switches """ + build_clib.initialize_options(self) + + # Visual Studio version + # Customize it by setting the env variable FDP_MSVC_VER + self.vs = os.environ.get("FDP_MSVC_VER", "2017") + + + def finalize_options(self): + """ Validate custom command line switches values """ + build_clib.finalize_options(self) + + def build_libraries(self, libraries): + # enough for LLDBagility; follow the installation instructions at FDPutils/README.md + return + # platform description refers at https://docs.python.org/2/library/sys.html#sys.platform + if not sys.platform == "win32": + raise ValueError("Can not build on a platform that is not native Windows") + + + assert self.vs in VS_VERSIONS, 'Unrecognized visual studio compiler version. Supported versions : %s ' % ",".join(VS_VERSIONS) + log.info("building FPD library with visual studio %s " % self.vs) + + _cwd = os.getcwd() + os.chdir(ROOT_DIRECTORY) + + # Cleanup old build folders + shutil.rmtree(os.path.join(ROOT_DIRECTORY, "out_x64"), ignore_errors=True) + shutil.rmtree(os.path.join(ROOT_DIRECTORY, "out_x86"), ignore_errors=True) + + # Windows build: this process requires few things: + # - CMake + MSVC installed + # - Run this command in an environment setup for MSVC + os.system(FDP_BUILD_SCRIPT(self.vs)) + if not os.path.exists(PATH_LIB64): + raise FileNotFoundError("Could not find %s : compilation step went wrong", PATH_LIB64) + if not os.path.exists(PATH_LIB32): + raise FileNotFoundError("Could not find %s : compilation step went wrong", PATH_LIB32) + + # copy compiled dll files into source dir + shutil.copy(PATH_LIB32, os.path.join(BINDING_DIRECTORY, "PyFDP", "FDP_x86.dll")) + shutil.copy(PATH_LIB64, os.path.join(BINDING_DIRECTORY, "PyFDP", "FDP_x64.dll")) + + os.chdir(_cwd) + +def dummy_src(): + return [] + +setuptools.setup( + provides=['PyFDP'], + packages = setuptools.find_packages(), + name="PyFDP", + version=VERSION, + description='FDP (Fast Debug Protocol) : KD client for debugging Windows VM without /DEBUG enabled.', + url='https://winbagility.github.io', + classifiers=[ + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', + 'Operating System :: Microsoft :: Windows', + 'Topic :: Software Development :: Debuggers', + ], + + requires=[], + + # PyFDP need to compile the client FDP dlls + # but there are not Python module compliant so + # the standard build_clib command will raise errors. That's + # why we hook build_clib in order to launch the cmake build script. + libraries=[( + 'PyFDP', dict( + package = 'PyFDP', + sources = dummy_src() + ), + )], + + cmdclass=dict( + build_clib=PyFDPCustomBuildClib, + ), + + # Tell setuptools not to zip into an egg file + # That's mandatory whenever there is a filepath involved + # (in our case via the LoadLibrary) + zip_safe=False, + + # We have two dlls to package with the python lib. + include_package_data=True, + package_data={ + "PyFDP": ["*.dll"], + } +) diff --git a/FDPutils/README.md b/FDPutils/README.md new file mode 100644 index 00000000..2ddabe8d --- /dev/null +++ b/FDPutils/README.md @@ -0,0 +1,44 @@ +# FDPutils +This folder contains a port of the original [Fast Debugging Protocol](https://winbagility.github.io/) (FDP) to macOS hosts and VirtualBox 5.2.14 and 6.0.8. The following sections detail the steps required to build VirtualBox with the FDP patch, build the FDP dylib and install PyFDP. + +The Releases section contains zipped VirtualBox 5.2.14 and 6.0.8 apps already built with the FDP patch; these still require for now to install MacPorts and `qt56` (see step 1.4 below). Launch the apps from the terminal with `open ` to catch possible errors. The 6.0.8 version may also require setting the shell variable `DYLD_LIBRARY_PATH="/FDPutils/VirtualBox-5.2.14_FDP_macOS.patch` (or `VirtualBox-6.0.8_FDP_macOS.patch`); +3. symlink or copy the FDP headers and sources with e.g. `ln -fs /FDPutils/FDP include/`, `ln -fs /FDPutils/FDP/include/* include/` and `ln -fs /FDPutils/FDP/FDP.c src/VBox/Debugger/`; +4. set up the VirtualBox dependencies: +- download and install [javaforosx.dmg](https://support.apple.com/kb/dl1572); +- download and install [Homebrew](https://brew.sh/), then `/usr/local/bin/brew install libidl glib openssl pkg-config`; +- download and install [MacPorts](https://www.macports.org/install.php), then `sudo /opt/local/bin/port install qt56`; +- download [Xcode_6.2.dmg](https://download.developer.apple.com/Developer_Tools/Xcode_6.2/Xcode_6.2.dmg) and mount it (e.g. `open Xcode_6.2.dmg`), then execute `tools/darwin.amd64/bin/xcode-6.2-extractor.sh`; +5. create a folder for the build (e.g. `mkdir /tmp/build`), then execute: +``` +$ ./configure --disable-hardening --disable-docs --with-openssl-dir=/usr/local/opt/openssl --with-xcode-dir=tools/darwin.amd64/xcode/v6.2/x.app --out-path=/tmp/build +``` +6. lastly, start the build process: +``` +$ source /tmp/build/env.sh +$ kmk +``` + +Building takes around 10/15 minutes; once finished, VirtualBox will be at `/tmp/build/darwin.amd64/release/dist/VirtualBox.app` and the script for loading drivers at `/tmp/build/darwin.amd64/release/dist/loadall.sh` To run this app on a different machine it should be enough to install the `qt56` MacPorts package as above. + +## 2. Building FDP +0. Download and install CMake (e.g. `brew install cmake`); +1. `cd` to `/FDPutils/build/` (assumed to be the working directory for all the next steps); +2. execute `cmake .`; +3. execute `make`; +4. `cp lib/libFDP.dylib ../PyFDP/PyFDP/`; +5. (optional, but suggested) execute `make testFDP`, start up a macOS virtual machine, execute `bin/testFDP ` and check that the tests succeed. + +## 3. Installing the Python bindings (PyFDP) +PyFDP should be compatible with both Python 2 and 3; if you are installing it for LLDBagility, pick the Python version used by your LLDB installation (likely Python 2). +1. `cd` to `/FDPutils/PyFDP/`; +2. then, execute either `sudo /usr/bin/python setup.py install` to install PyFDP for system Python or `/usr/local/bin/python2 -m pip install .` for Homebrew Python 2; +3. (optional, but suggested) start a Python shell, execute `import PyFDP` and check that it succeeds. diff --git a/FDPutils/TestFDP/testFDP.c b/FDPutils/TestFDP/testFDP.c new file mode 100644 index 00000000..e22b233e --- /dev/null +++ b/FDPutils/TestFDP/testFDP.c @@ -0,0 +1,1809 @@ +/* + MIT License + + Copyright (c) 2015 Nicolas Couffin ncouffin@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +// #include "windows.h" + +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "FDP.h" + +int iTimerDelay = 2; +bool TimerGo = false; +bool TimerOut = false; + +void TimerSetDelay(int iNewTimerDelay) +{ + iTimerDelay = iNewTimerDelay; +} + +int TimerGetDelay() +{ + return iTimerDelay; +} + +void* TimerRoutine(LPVOID lpParam) +{ + while (true){ + while (TimerGo == false){ + usleep(1000 * 10); + } + TimerGo = false; + usleep(1000 * iTimerDelay * 1000); + TimerOut = true; + } +} + +#define TEST_MSR_VALUE 0xFFFFFFFFFFFFFFFF +bool testReadWriteMSR(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + FDP_Pause(pFDP); + uint64_t originalMSRValue; + uint64_t modMSRValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &originalMSRValue) == false){ + printf("Failed to read MSRValue !\n"); + return false; + } + + if (FDP_WriteMsr(pFDP, 0, MSR_LSTAR, TEST_MSR_VALUE) == false){ + printf("Failed to write MSRValue !\n"); + return false; + } + + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &modMSRValue) == false){ + printf("Failed to read MSRValue !\n"); + return false; + } + + if (modMSRValue != TEST_MSR_VALUE){ + printf("MSRValue doesn't match !\n"); + return false; + } + + if (FDP_WriteMsr(pFDP, 0, MSR_LSTAR, originalMSRValue) == false){ + printf("Failed to write MSRValue !\n"); + return false; + } + + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &modMSRValue) == false){ + printf("Failed to read MSRValue !\n"); + return false; + } + + if (modMSRValue != originalMSRValue){ + printf("MSRValue doesn't match !\n"); + return false; + } + FDP_Resume(pFDP); + printf("[OK]\n"); + return true; +} + +#define TEST_REGISTER_VALUE 0xDEADBEEFDEADBEEF +bool testReadWriteRegister(FDP_SHM* pFDP){ + printf("%s ...", __FUNCTION__); + + if (FDP_Pause(pFDP) == false){ + return false; + } + + uint64_t originalRAXValue; + if (FDP_ReadRegister(pFDP, 0, FDP_RAX_REGISTER, &originalRAXValue) == false){ + printf("Failed to read RegisterValue !\n"); + return false; + } + + if (FDP_WriteRegister(pFDP, 0, FDP_RAX_REGISTER, TEST_REGISTER_VALUE) == false){ + printf("Failed to write RegisterValue !\n"); + return false; + } + + uint64_t modRAXValue; + if (FDP_ReadRegister(pFDP, 0, FDP_RAX_REGISTER, &modRAXValue) == false){ + printf("Failed to read RegisterValue !\n"); + return false; + } + + if (modRAXValue != TEST_REGISTER_VALUE){ + printf("RegisterValue doesn't match %llx != %lx!\n", modRAXValue, TEST_REGISTER_VALUE); + return false; + } + + if (FDP_WriteRegister(pFDP, 0, FDP_RAX_REGISTER, originalRAXValue) == false){ + printf("Failed to write RegisterValue !\n"); + return false; + } + printf("[OK]\n"); + return true; +} + + +bool testReadWritePhysicalMemory(FDP_SHM* pFDP){ + printf("%s ...", __FUNCTION__); + uint8_t originalPage[4096]; + if (FDP_ReadPhysicalMemory(pFDP, originalPage, 4096, 4096 * 12) == false){ + printf("Failed to read PhysicalMemory !\n"); + return false; + } + + uint8_t garbagePage[4096]; + memset(garbagePage, 0xCA, 4096); + if (FDP_WritePhysicalMemory(pFDP, garbagePage, 4096, 4096 * 12) == false){ + printf("Failed to write PhysicalMemory !\n"); + return false; + } + + uint8_t modPage[4096]; + if (FDP_ReadPhysicalMemory(pFDP, modPage, 4096, 4096 * 12) == false){ + printf("Failed to read PhysicalMemory !\n"); + return false; + } + + if (memcmp(garbagePage, modPage, 4096) != 0){ + printf("Failed to compare garbagePage and modPage !\n"); + return false; + } + + if (FDP_WritePhysicalMemory(pFDP, originalPage, 4096, 4096 * 12) == false){ + printf("Failed to write PhysicalMemory !\n"); + return false; + } + printf("[OK]\n"); + return true; +} + +bool testReadWriteVirtualMemorySpeed(FDP_SHM* pFDP){ + printf("%s ...", __FUNCTION__); + + uint64_t tempVirtualAddress = 0; + uint64_t originalMSRValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &originalMSRValue) == false){ + printf("Failed to read MSRValue !\n"); + return false; + } + tempVirtualAddress = originalMSRValue & 0xFFFFFFFFFFFFF000; + TimerOut = false; + TimerGo = true; + uint64_t ReadCount = 0; + while (TimerOut == false){ + uint8_t OriginalPage[4096]; + if (FDP_ReadVirtualMemory(pFDP, 0, OriginalPage, 4096, tempVirtualAddress) == false){ + printf("Failed to read VirtualMemory !\n"); + return false; + } + ReadCount++; + } + + int ReadCountPerSecond = (int)ReadCount / iTimerDelay; + if (ReadCountPerSecond < 400000){ + printf("Too Slow !\n"); + return false; + } + + printf("[OK] %d/s\n", ReadCountPerSecond); + return true; +} + +bool testReadWritePhysicalMemorySpeed(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + TimerOut = false; + TimerGo = true; + uint64_t ReadCount = 0; + while (TimerOut == false) { + uint8_t OriginalPage[4096]; + if (FDP_ReadPhysicalMemory(pFDP, OriginalPage, sizeof(OriginalPage), 0) == false) { + printf("Failed to read VirtualMemory !\n"); + return false; + } + ReadCount++; + } + + int ReadCountPerSecond = (int)ReadCount / iTimerDelay; + if (ReadCountPerSecond < 400000) { + printf("Too Slow !\n"); + return false; + } + + printf("[OK] %d/s\n", ReadCountPerSecond); + return true; +} + +bool testReadLargePhysicalMemory(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + uint8_t *pBuffer = (uint8_t*)malloc((50 * _1M)); + if (pBuffer == NULL) { + printf("Failed to malloc\n"); + return false; + } + + uint64_t Cr3; + FDP_ReadRegister(pFDP, 0, FDP_CR3_REGISTER, &Cr3); + + if (FDP_ReadPhysicalMemory(pFDP, pBuffer, 49 * _1M, Cr3) == false) { + printf("Failed to FDP_ReadPhysicalMemory\n"); + free(pBuffer); + return false; + } + + free(pBuffer); + printf("[OK]\n"); + return true; +} + +//TODO: find contig virtual memory... +bool testReadLargeVirtualMemory(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + uint8_t *pBuffer = (uint8_t*)malloc(50 * _1M); + uint64_t originalMSRValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &originalMSRValue) == false) { + printf("Failed to read MSRValue !\n"); + free(pBuffer); + return false; + } + + if (FDP_ReadVirtualMemory(pFDP, 0, pBuffer, 1 * _1M, originalMSRValue) == false) { + free(pBuffer); + return false; + } + + free(pBuffer); + printf("[OK]\n"); + return true; +} + +bool testReadWriteVirtualMemory(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + uint64_t tempVirtualAddress = 0; + + uint64_t originalMSRValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &originalMSRValue) == false){ + printf("Failed to read MSRValue !\n"); + return false; + } + tempVirtualAddress = originalMSRValue & 0xFFFFFFFFFFFFF000; + + uint8_t originalPage[4096]; + if (FDP_ReadVirtualMemory(pFDP, 0, originalPage, 4096, tempVirtualAddress) == false){ + printf("Failed to read VirtualMemory !\n"); + return false; + } + + uint8_t garbagePage[4096]; + memset(garbagePage, 0xCA, 4096); + for (int i = 0; i <= 4096; i++){ + if (FDP_WriteVirtualMemory(pFDP, 0, garbagePage, i, tempVirtualAddress) == false){ + printf("Failed to write PhysicalMemory !\n"); + return false; + } + } + + uint8_t modPage[4096]; + if (FDP_ReadVirtualMemory(pFDP, 0, modPage, 4096, tempVirtualAddress) == false){ + printf("Failed to read VirtualMemory !\n"); + return false; + } + + if (memcmp(garbagePage, modPage, 4096) != 0){ + printf("Failed to compare garbagePage and modPage !\n"); + return false; + } + + for (int i = 0; i <= 4096; i++){ + if (FDP_WriteVirtualMemory(pFDP, 0, originalPage, i, tempVirtualAddress) == false){ + printf("Failed to write PhysicalMemory !\n"); + return false; + } + } + printf("[OK]\n"); + return true; +} + +bool testGetStatePerformance(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + TimerOut = false; + TimerGo = true; + uint64_t ReadCount = 0; + while (TimerOut == false){ + FDP_State state; + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to get state !\n"); + return false; + } + ReadCount++; + } + + int ReadPerSecond = (int)(ReadCount / TimerGetDelay()); + printf("[OK] %d/s\n", ReadPerSecond); + return true; +} + +bool testVirtualSyscallBP(FDP_SHM* pFDP, FDP_BreakpointType BreakpointType) +{ + printf("%s ...", __FUNCTION__); + + FDP_Pause(pFDP); + + uint32_t CPUCount; + if (FDP_GetCpuCount(pFDP, &CPUCount) == false){ + printf("Failed to get CPU count !\n"); + return false; + } + + uint64_t originalMSRValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &originalMSRValue) == false){ + printf("Failed to read MSRValue !\n"); + return false; + } + + int breakpointId = FDP_SetBreakpoint(pFDP, 0, BreakpointType, -1, FDP_EXECUTE_BP, FDP_VIRTUAL_ADDRESS, originalMSRValue, 1, FDP_NO_CR3); + if (breakpointId < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + + int i = 0; + TimerOut = false; + TimerGo = true; + while (TimerOut == false){ + if (FDP_GetStateChanged(pFDP)){ + FDP_State state; + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to get state !\n"); + return false; + } + if (state & FDP_STATE_PAUSED + && state & FDP_STATE_BREAKPOINT_HIT + && !(state & FDP_STATE_DEBUGGER_ALERTED)){ + i++; + + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to get state !\n"); + return false; + } + + if (!(state & FDP_STATE_DEBUGGER_ALERTED)){ + printf("!(state & FDP_STATE_DEBUGGER_ALERTED) (state %02x ) !!!\n", state); + } + + if (FDP_UnsetBreakpoint(pFDP, breakpointId) == false){ + printf("Failed to remove page breakpoint !\n"); + return false; + } + + for (uint32_t c = 0; c < CPUCount; c++){ + if (FDP_SingleStep(pFDP, c) == false){ + printf("Failed to single step !\n"); + return false; + } + } + + breakpointId = FDP_SetBreakpoint(pFDP, 0, BreakpointType, -1, FDP_EXECUTE_BP, FDP_VIRTUAL_ADDRESS, originalMSRValue, 1, FDP_NO_CR3); + if (breakpointId < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + } + } + } + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + } + + if (FDP_UnsetBreakpoint(pFDP, breakpointId) == false){ + printf("Failed to remove page breakpoint !\n"); + return false; + } + + printf("[OK] %d/s\n", (i / TimerGetDelay())); + return true; +} + +bool testPhysicalSyscallBP(FDP_SHM* pFDP, FDP_BreakpointType BreakpointType){ + printf("%s ...", __FUNCTION__); + + uint64_t originalMSRValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &originalMSRValue) == false){ + printf("Failed to read MSRValue !\n"); + return false; + } + + uint64_t physicalLSTAR; + if (FDP_VirtualToPhysical(pFDP, 0, originalMSRValue, &physicalLSTAR) == false){ + printf("Failed to convert virtual to physical !\n"); + return false; + } + + + int breakpointId = FDP_SetBreakpoint(pFDP, 0, BreakpointType, -1, FDP_EXECUTE_BP, FDP_PHYSICAL_ADDRESS, physicalLSTAR, 1, FDP_NO_CR3); + if (breakpointId < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + + int i = 0; + TimerOut = false; + TimerGo = true; + while (TimerOut == false){ + if (FDP_GetStateChanged(pFDP)){ + FDP_State state; + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to get state !\n"); + return false; + } + if (state & FDP_STATE_BREAKPOINT_HIT + && !(state & FDP_STATE_DEBUGGER_ALERTED)){ + i++; + uint8_t state = 0; + if (FDP_UnsetBreakpoint(pFDP, breakpointId) == false){ + printf("Failed to remove page breakpoint !\n"); + return false; + } + + if (FDP_SingleStep(pFDP, 0) == false){ + printf("Failed to single step !\n"); + } + + breakpointId = FDP_SetBreakpoint(pFDP, 0, BreakpointType, -1, FDP_EXECUTE_BP, FDP_PHYSICAL_ADDRESS, physicalLSTAR, 1, FDP_NO_CR3); + if (breakpointId < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + } + } + } + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + } + + if (FDP_UnsetBreakpoint(pFDP, breakpointId) == false){ + printf("Failed to remove page breakpoint !\n"); + return false; + } + + printf("[OK] %d/s\n", (i / TimerGetDelay())); + return true; +} + + +bool testMultiplePhysicalSyscallBP(FDP_SHM* pFDP, FDP_BreakpointType BreakpointType){ + printf("%s ...", __FUNCTION__); + + uint64_t originalMSRValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &originalMSRValue) == false){ + printf("Failed to read MSRValue !\n"); + return false; + } + + uint64_t physicalLSTAR; + if (FDP_VirtualToPhysical(pFDP, 0, originalMSRValue, &physicalLSTAR) == false){ + printf("Failed to convert virtual to physical !\n"); + return false; + } + + int breakpointId[10]; + for (int j = 0; j < 10; j++){ + breakpointId[j] = FDP_SetBreakpoint(pFDP, 0, BreakpointType, -1, FDP_EXECUTE_BP, FDP_PHYSICAL_ADDRESS, physicalLSTAR + j, 1, FDP_NO_CR3); + if (breakpointId[j] < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + + int i = 0; + while (i < 10){ + FDP_State state; + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to get state !\n"); + return false; + } + if (state & FDP_STATE_BREAKPOINT_HIT + && !(state & FDP_STATE_DEBUGGER_ALERTED)){ + i++; + + for (int j = 0; j < 10; j++){ + if (FDP_UnsetBreakpoint(pFDP, breakpointId[j]) == false){ + printf("Failed to remove page breakpoint !\n"); + return false; + } + } + + if (FDP_SingleStep(pFDP, 0) == false){ + printf("Failed to single step !\n"); + } + + for (int j = 0; j < 10; j++){ + breakpointId[j] = FDP_SetBreakpoint(pFDP, 0, BreakpointType, -1, FDP_EXECUTE_BP, FDP_PHYSICAL_ADDRESS, physicalLSTAR + j, 1, FDP_NO_CR3); + if (breakpointId[j] < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + } + } + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + } + + for (int j = 0; j < 10; j++){ + if (FDP_UnsetBreakpoint(pFDP, breakpointId[j]) == false){ + printf("Failed to remove page breakpoint !\n"); + return false; + } + } + + printf("[OK]\n"); + return true; +} + + +bool testMultipleVirtualSyscallBP(FDP_SHM* pFDP, FDP_BreakpointType BreakpointType){ + printf("%s ...", __FUNCTION__); + + uint32_t CPUCount; + if (FDP_GetCpuCount(pFDP, &CPUCount) == false){ + printf("Failed to get CPU count !\n"); + return false; + } + + uint64_t originalMSRValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &originalMSRValue) == false){ + printf("Failed to read MSRValue !\n"); + return false; + } + + int breakpointId[10]; + for (int j = 0; j < 10; j++){ + breakpointId[j] = FDP_SetBreakpoint(pFDP, 0, BreakpointType, -1, FDP_EXECUTE_BP, FDP_VIRTUAL_ADDRESS, originalMSRValue + j, 1, FDP_NO_CR3); + if (breakpointId[j] < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + + int i = 0; + while (i < 10){ + FDP_State state; + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to get state !\n"); + return false; + } + if (state & FDP_STATE_BREAKPOINT_HIT + && !(state & FDP_STATE_DEBUGGER_ALERTED)){ + i++; + + for (int j = 0; j < 10; j++){ + if (FDP_UnsetBreakpoint(pFDP, breakpointId[j]) == false){ + printf("Failed to remove page breakpoint !\n"); + return false; + } + } + + for (uint32_t c = 0; c < CPUCount; c++){ + if (FDP_SingleStep(pFDP, c) == false){ + printf("Failed to single step !\n"); + return false; + } + } + + for (int j = 0; j < 10; j++){ + breakpointId[j] = FDP_SetBreakpoint(pFDP, 0, BreakpointType, -1, FDP_EXECUTE_BP, FDP_VIRTUAL_ADDRESS, originalMSRValue + j, 1, FDP_NO_CR3); + if (breakpointId[j] < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + } + } + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + } + + for (int j = 0; j < 10; j++){ + if (FDP_UnsetBreakpoint(pFDP, breakpointId[j]) == false){ + printf("Failed to remove page breakpoint !\n"); + return false; + } + } + + printf("[OK]\n"); + return true; +} + + + +bool testLargeVirtualPageSyscallBP(FDP_SHM* pFDP){ + printf("%s ...", __FUNCTION__); + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + return false; + } + uint64_t originalMSRValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &originalMSRValue) == false){ + printf("Failed to read MSRValue !\n"); + return false; + } + + int breakpointId = FDP_SetBreakpoint(pFDP, 0, FDP_PAGEHBP, -1, FDP_EXECUTE_BP, FDP_VIRTUAL_ADDRESS, originalMSRValue, 4096*30, FDP_NO_CR3); + if (breakpointId < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + + int i = 0; + while (i < 10){ + FDP_State state; + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to get state !\n"); + return false; + } + if (state & FDP_STATE_BREAKPOINT_HIT + && !(state & FDP_STATE_DEBUGGER_ALERTED)){ + i++; + //FDP_State state;// = 0; + printf("."); + if (FDP_UnsetBreakpoint(pFDP, breakpointId) == false){ + printf("Failed to remove page breakpoint !\n"); + return false; + } + + if (FDP_SingleStep(pFDP, 0) == false){ + printf("Failed to single step !\n"); + } + + breakpointId = FDP_SetBreakpoint(pFDP, 0, FDP_PAGEHBP, -1, FDP_EXECUTE_BP, FDP_VIRTUAL_ADDRESS, originalMSRValue, 4096 * 30, FDP_NO_CR3); + if (breakpointId < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + } + } + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + } + + if (FDP_UnsetBreakpoint(pFDP, breakpointId) == false){ + printf("Failed to remove page breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + + printf("[OK]\n"); + return true; +} + + +bool testLargePhysicalPageSyscallBP(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + uint64_t originalMSRValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &originalMSRValue) == false){ + printf("Failed to read MSRValue !\n"); + return false; + } + + uint64_t physicalLSTAR; + if (FDP_VirtualToPhysical(pFDP, 0, originalMSRValue, &physicalLSTAR) == false){ + printf("Failed to convert virtual to physical !\n"); + return false; + } + + int breakpointId = FDP_SetBreakpoint(pFDP, 0, FDP_PAGEHBP, -1, FDP_EXECUTE_BP, FDP_PHYSICAL_ADDRESS, physicalLSTAR, 4096 * 30, FDP_NO_CR3); + if (breakpointId < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + + int i = 0; + while (i < 10){ + if (FDP_GetStateChanged(pFDP) == true){ + FDP_State state; + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to get state !\n"); + return false; + } + if (state & FDP_STATE_BREAKPOINT_HIT + && !(state & FDP_STATE_DEBUGGER_ALERTED)){ + i++; + //FDP_State state = 0; + if (FDP_UnsetBreakpoint(pFDP, breakpointId) == false){ + printf("Failed to remove page breakpoint !\n"); + return false; + } + + if (FDP_SingleStep(pFDP, 0) == false){ + printf("Failed to single step !\n"); + } + + breakpointId = FDP_SetBreakpoint(pFDP, 0, FDP_PAGEHBP, -1, FDP_EXECUTE_BP, FDP_PHYSICAL_ADDRESS, physicalLSTAR, 4096 * 30, FDP_NO_CR3); + if (breakpointId < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + } + } + } + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + } + + if (FDP_UnsetBreakpoint(pFDP, breakpointId) == false){ + printf("Failed to remove page breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + + printf("[OK]\n"); + return true; +} + + +bool testMultiCpu(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + uint32_t CPUCount; + if (FDP_GetCpuCount(pFDP, &CPUCount) == false){ + printf("Failed to get CPU count !\n"); + return false; + } + + uint64_t oldRAXValue = 0; + for (uint32_t c = 0; c < CPUCount; c++){ + + uint64_t RAXValue; + if (FDP_ReadRegister(pFDP, c, FDP_RAX_REGISTER, &RAXValue) == false){ + printf("Failed to read register value!\n"); + return false; + } + + if (oldRAXValue == RAXValue){ + //printf("Failed to switch CPU!\n"); + //return false; + } + } + + + printf("[OK]\n"); + return true; +} + + + + +bool testState(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume!\n"); + system("pause"); + return false; + } + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + system("pause"); + return false; + } + + FDP_State state; + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to get state !\n"); + system("pause"); + return false; + } + + if (!(state & FDP_STATE_PAUSED)){ + printf("1. State != STATE_PAUSED (state %02x)!\n", state); + system("pause"); + return false; + } + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + system("pause"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to pause !\n"); + system("pause"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to pause !\n"); + system("pause"); + return false; + } + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + system("pause"); + return false; + } + + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to get state !\n"); + system("pause"); + return false; + } + + if (!(state & FDP_STATE_PAUSED)){ + printf("3. State != STATE_PAUSED (state %02x)!\n", state); + system("pause"); + return false; + } + + uint64_t originalMSRValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &originalMSRValue) == false){ + printf("Failed to read MSRValue !\n"); + return false; + } + + int breakpointId = FDP_SetBreakpoint(pFDP, 0, FDP_SOFTHBP, -1, FDP_EXECUTE_BP, FDP_VIRTUAL_ADDRESS, originalMSRValue, 1, FDP_NO_CR3); + if (breakpointId < 0){ + printf("Failed to insert page breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume"); + return false; + } + + while (true){ + FDP_State state; + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to get state !\n"); + return false; + } + + if (state & FDP_STATE_BREAKPOINT_HIT + && !(state & FDP_STATE_DEBUGGER_ALERTED)){ + break; + } + else{ + //printf("state %02x\n", state); + } + usleep(1000 * 100); + } + + if (FDP_UnsetBreakpoint(pFDP, breakpointId) == false){ + printf("Failed to clear breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + + printf("[OK]\n"); + return true; +} + +bool testDebugRegisters(FDP_SHM* pFDP){ + printf("%s ...", __FUNCTION__); + + if (FDP_Pause(pFDP) == false){ + printf("Failed to FDP_Pause !\n"); + return false; + } + + uint64_t oldDR0Value; + uint64_t oldDR7Value; + if (FDP_ReadRegister(pFDP, 0, FDP_DR0_REGISTER, &oldDR0Value) == false){ + return false; + } + if (FDP_ReadRegister(pFDP, 0, FDP_DR7_REGISTER, &oldDR7Value) == false){ + return false; + } + + uint64_t LSTARValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &LSTARValue) == false){ + printf("Failed to read FDP_ReadMsr !\n"); + return false; + } + if (FDP_WriteRegister(pFDP, 0, FDP_DR0_REGISTER, LSTARValue) == false){ + printf("Failed to write FDP_WriteRegister!\n"); + return false; + } + + if (FDP_WriteRegister(pFDP, 0, FDP_DR7_REGISTER, 0x0000000000000403) == false){ + printf("Failed to write FDP_WriteRegister!\n"); + return false; + } + + if (FDP_Resume(pFDP) == 0){ + return false; + } + + TimerOut = false; + TimerGo = true; + TimerSetDelay(5); + uint64_t i = 0; + while (TimerOut == false){ + if (FDP_GetStateChanged(pFDP) == true){ + FDP_State state; + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to FDP_GetState !\n"); + return false; + } + if (state & FDP_STATE_BREAKPOINT_HIT){ + i++; + if (FDP_SingleStep(pFDP, 0) == false){ + return false; + } + if (FDP_Resume(pFDP) == false){ + return false; + } + } + } + } + + if (FDP_WriteRegister(pFDP, 0, FDP_DR0_REGISTER, oldDR0Value) == false){ + return false; + } + + if (FDP_WriteRegister(pFDP, 0, FDP_DR7_REGISTER, oldDR7Value) == false){ + return false; + } + + if (FDP_Resume(pFDP) == false){ + return false; + } + + int BreakpointPerSecond = (int)i / TimerGetDelay(); + printf("[OK] %d/s\n", BreakpointPerSecond); + return true; +} + +bool threadRunning; + +void* testStateThread(LPVOID lpParam) +{ + FDP_SHM* pFDP = (FDP_SHM*)lpParam; + while (threadRunning){ + FDP_State state; + FDP_GetState(pFDP, &state); + } + return 0; +} + +void* testReadRegisterThread(LPVOID lpParam) +{ + FDP_SHM* pFDP = (FDP_SHM*)lpParam; + while (threadRunning){ + uint64_t RegisterValue; + FDP_ReadRegister(pFDP, 0, FDP_RAX_REGISTER, &RegisterValue); + } + return 0; +} + +void* testReadMemoryThread(LPVOID lpParam) +{ + FDP_SHM* pFDP = (FDP_SHM*)lpParam; + while (threadRunning){ + uint8_t TempBuffer[1024]; + FDP_ReadPhysicalMemory(pFDP, TempBuffer, 1024, 0); + } + return 0; +} + +bool testMultiThread(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + threadRunning = true; + + pthread_t t1; + pthread_create(&t1, NULL, testStateThread, pFDP); + + pthread_t t2; + pthread_create(&t2, NULL, testReadRegisterThread, pFDP); + + pthread_t t3; + pthread_create(&t3, NULL, testReadMemoryThread, pFDP); + + + // HANDLE hThread1 = CreateThread(NULL, 0, testStateThread, pFDP, 0, NULL); + // HANDLE hThread2 = CreateThread(NULL, 0, testReadRegisterThread, pFDP, 0, NULL); + // HANDLE hThread3 = CreateThread(NULL, 0, testReadMemoryThread, pFDP, 0, NULL); + + usleep(1000 * 2000); + + threadRunning = false; + + usleep(1000 * 100); + + + pthread_join(t1, NULL); + pthread_join(t2, NULL); + pthread_join(t3, NULL); + + // TerminateThread(hThread1, 0); + // TerminateThread(hThread2, 0); + // TerminateThread(hThread3, 0); + + printf("[OK]\n"); + return true; + +} + +bool testUnsetBreakpoint(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + if (FDP_Pause(pFDP) == false){ + return false; + } + + for (uint8_t b = 0; b < FDP_MAX_BREAKPOINT; b++){ + FDP_UnsetBreakpoint(pFDP, b); + } + + if (FDP_Resume(pFDP) == 0){ + return false; + } + + printf("[OK]\n"); + return true; +} + +bool testSingleStep(FDP_SHM* pFDP){ + printf("%s ...", __FUNCTION__); + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + return false; + } + uint64_t SingleStepCount = 0; + TimerSetDelay(30); + TimerOut = false; + TimerGo = true; + while (TimerOut == false){ + FDP_SingleStep(pFDP, 0); + SingleStepCount++; + } + FDP_Resume(pFDP); + + printf("[OK]\n"); + return true; +} + +bool testSingleStepSpeed(FDP_SHM* pFDP){ + printf("%s ...", __FUNCTION__); + + FDP_Pause(pFDP); + + uint64_t SingleStepCount = 0; + TimerSetDelay(3); + TimerOut = false; + TimerGo = true; + while (TimerOut == false){ + FDP_SingleStep(pFDP, 0); + SingleStepCount++; + } + + int SingleStepCountPerSecond = (int)SingleStepCount / TimerGetDelay(); + + if (SingleStepCountPerSecond < 50000){ + printf("Too slow !\n"); + return false; + } + FDP_Resume(pFDP); + printf("[OK] %d/s\n", SingleStepCountPerSecond); + return true; +} + +bool testSaveRestore(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + system("pause"); + return false; + } + + if (FDP_Save(pFDP) == false){ + printf("Failed to save !\n"); + return false; + } + + uint64_t OriginalRipValue; + if (FDP_ReadRegister(pFDP, 0, FDP_RIP_REGISTER, &OriginalRipValue) == false){ + printf("Failed to FDP_ReadRegister\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + + for (int i = 0; i < 10; i++){ + usleep(1000 * 3000); + + if (FDP_Restore(pFDP) == false){ + printf("Failed to FDP_Restore !\n"); + return false; + } + + uint64_t NewRipValue; + if (FDP_ReadRegister(pFDP, 0, FDP_RIP_REGISTER, &NewRipValue) == false){ + printf("Failed to FDP_ReadRegister\n"); + return false; + } + + if (OriginalRipValue != NewRipValue){ + printf("OriginalRipValue != NewRipValue\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to FDP_Resume !\n"); + return false; + + } + + //Wait for Rip change + uint64_t OldRipValue; + uint64_t RipValue; + FDP_ReadRegister(pFDP, 0, FDP_RIP_REGISTER, &OldRipValue); + while (true){ + FDP_ReadRegister(pFDP, 0, FDP_RIP_REGISTER, &RipValue); + if (RipValue != OldRipValue){ + break; + } + } + + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to FDP_Resume !\n"); + return false; + } + + printf("[OK]\n"); + return true; +} + +bool testReadAllPhysicalMemory(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + uint64_t PhysicalAddress = 0; + char Buffer[4096]; + uint64_t PhysicalMaxAddress = 0; + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + return false; + } + if (FDP_GetPhysicalMemorySize(pFDP, &PhysicalMaxAddress) == false){ + printf("Failed to FDP_GetPhysicalMemorySize\n"); + return false; + } + while (PhysicalAddress < PhysicalMaxAddress){ + if (FDP_ReadPhysicalMemory(pFDP, (uint8_t *)Buffer, sizeof(Buffer), PhysicalAddress) == false){ + //Some PhysicalAddress aren't readeable + //printf("Failed to FDP_ReadPhysicalMemory (%p)\n", PhysicalAddress); + //return false; + } + PhysicalAddress += sizeof(Buffer); + } + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + + printf("[OK]\n"); + return true; +} + + +bool testReadWriteAllPhysicalMemory(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + uint64_t PhysicalAddress = 0; + char Buffer[4096]; + uint64_t PhysicalMaxAddress = 0; + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + return false; + } + if (FDP_GetPhysicalMemorySize(pFDP, &PhysicalMaxAddress) == false){ + printf("Failed to FDP_GetPhysicalMemorySize\n"); + return false; + } + + while (PhysicalAddress < PhysicalMaxAddress){ + if (FDP_ReadPhysicalMemory(pFDP, (uint8_t *)Buffer, sizeof(Buffer), PhysicalAddress) == true){ + FDP_WritePhysicalMemory(pFDP, (uint8_t *)Buffer, sizeof(Buffer), PhysicalAddress); + } + PhysicalAddress += sizeof(Buffer); + } + if (FDP_Resume(pFDP) == false){ + printf("Failed to resume !\n"); + return false; + } + + printf("[OK]\n"); + return true; +} + + +/*#define EPROCESS_ACTIVEPROCESSLIST_OFF 0x2F0 +#define EPROCESS_PROCESSNAME_OFF 0x448 +#define EPROCESS_PROCESSNAME_SIZE 15 +uint64_t GetPsActiveProcessHead(){ + return 0xfffff80002883b90; +} + +uint64_t BreakOnKiSystemCall64(FDP_SHM *pFDP){ + + FDP_Pause(pFDP); + + uint64_t KiSystemCall64; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &KiSystemCall64) == false){ + printf("Failed to read FDP_ReadMsr !\n"); + return false; + } + + int breakpointId = FDP_SetBreakpoint(pFDP, 0, FDP_SOFTHBP, -1, FDP_EXECUTE_BP, FDP_VIRTUAL_ADDRESS, KiSystemCall64, 1); + if (breakpointId < 0){ + printf("Failed to FDP_SetBreakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false){ + printf("Failed to FDP_Resume"); + return false; + } + + //TODO: + while (true){ + if (FDP_GetStateChanged(pFDP) == true){ + FDP_State state; + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to FDP_GetState !\n"); + return false; + } + if (state & FDP_STATE_BREAKPOINT_HIT + && !(state & FDP_STATE_DEBUGGER_ALERTED)){ + break; + } + } + } + + FDP_UnsetBreakpoint(pFDP, breakpointId); + + printf("BreakOnKiSystemCall64\n"); + + return true; +} + + +bool MonitorProcessList(FDP_SHM *pFDP){ + bool bRunning = true; + + FDP_Resume(pFDP); + printf("Saving safe state..."); + FDP_Pause(pFDP); + FDP_Save(pFDP); + FDP_Resume(pFDP); + printf("DONE !\n"); + + uint64_t ReadCount = 0; + + ReadCount = 0; + TimerOut = false; + TimerGo = true; + iTimerDelay = 1; + while (true){ + if (TimerOut == true){ + printf("%d\n", ReadCount / TimerGetDelay()); + ReadCount = 0; + TimerOut = false; + TimerGo = true; + } + uint64_t PsActiveProcessHead = GetPsActiveProcessHead(); + uint64_t FirstProcess = PsActiveProcessHead - EPROCESS_ACTIVEPROCESSLIST_OFF; + + if (FDP_ReadVirtualMemory(pFDP, 0, (uint8_t*)&FirstProcess, sizeof(FirstProcess), FirstProcess + EPROCESS_ACTIVEPROCESSLIST_OFF) == false){ + break; + } + FirstProcess = FirstProcess - EPROCESS_ACTIVEPROCESSLIST_OFF; + uint64_t CurrentProcess = FirstProcess; + while (true){ + char ProcessName[EPROCESS_PROCESSNAME_SIZE]; + if (FDP_ReadVirtualMemory(pFDP, 0, (uint8_t*)ProcessName, EPROCESS_PROCESSNAME_SIZE, CurrentProcess + EPROCESS_PROCESSNAME_OFF) == false){ + break; + } + + if (strcmp(ProcessName, "notepad.exe") == 0){ + printf("Restoring state..."); + FDP_Pause(pFDP); + FDP_Restore(pFDP); + FDP_Resume(pFDP); + printf("DONE !\n"); + break; + } + + if (FDP_ReadVirtualMemory(pFDP, 0, (uint8_t*)&CurrentProcess, sizeof(CurrentProcess), CurrentProcess + EPROCESS_ACTIVEPROCESSLIST_OFF) == false){ + break; + } + CurrentProcess = CurrentProcess - EPROCESS_ACTIVEPROCESSLIST_OFF; + + if (CurrentProcess == FirstProcess){ + ReadCount++; + break; + } + } + } + return 0; +} + +uint64_t FDP_VirutalChecksum(FDP_SHM *pFDP, uint64_t VirtualAddress, uint32_t DataSize) +{ + uint64_t ChecksumResult = 0xDEADDEADDEADDEAD; + uint64_t Buffer[4096 / 8]; + for (uint32_t i = 0; i < (DataSize / 4096); i++){ + printf("."); + FDP_ReadVirtualMemory(pFDP, 0, (uint8_t*)Buffer, 4096, VirtualAddress + (i * 4096)); + + for (int j = 0; j < (4096 / 8); j++){ + ChecksumResult = (ChecksumResult ^ Buffer[j]); + } + } + return ChecksumResult; +} + +bool testNTChecksum(FDP_SHM *pFDP){ + uint64_t NTVirtualAddress = 0xfffff80097e08018; + while (true){ + uint64_t CurrentChecksum = FDP_VirutalChecksum(pFDP, NTVirtualAddress, 0x80d3e8); + printf("%p\n", CurrentChecksum); + usleep(1000 * 1000); + } +} + +bool MonitorNtCreateFile(FDP_SHM* pFDP){ + printf("%s ...", __FUNCTION__); + + if (FDP_Pause(pFDP) == false){ + printf("Failed to pause !\n"); + } + + uint64_t oldDR0Value; + uint64_t oldDR7Value; + if (FDP_ReadRegister(pFDP, 0, FDP_DR0_REGISTER, &oldDR0Value) == false){ + return false; + } + if (FDP_ReadRegister(pFDP, 0, FDP_DR7_REGISTER, &oldDR7Value) == false){ + return false; + } + + uint64_t NtCreateFileAddress = 0xfffff80098289270; //NtCreateFIile + //uint64_t NtCreateFileAddress = 0xfffff80098289204; //NtOpenFile + //uint64_t NtCreateFileAddress = 0xfffff80098226100; //NtWriteFile + + if (FDP_WriteRegister(pFDP, 0, FDP_DR0_REGISTER, NtCreateFileAddress) == false){ + printf("Failed to write DR0!\n"); + return false; + } + + if (FDP_WriteRegister(pFDP, 0, FDP_DR7_REGISTER, 0x0000000000000403) == false){ + printf("Failed to write DR0!\n"); + return false; + } + + if (FDP_Resume(pFDP) == 0){ + return false; + } + + printTime(); + int i = 0; + while (i < 10000000000){ + if (FDP_GetStateChanged(pFDP)){ + FDP_State state; + if (FDP_GetState(pFDP, &state) == false){ + printf("Failed to get state !\n"); + return false; + } + if (state & FDP_STATE_BREAKPOINT_HIT + && !(state & FDP_STATE_DEBUGGER_ALERTED)){ + i++; + + + uint64_t R8Value; + FDP_ReadRegister(pFDP, 0, FDP_R8_REGISTER, &R8Value); + + uint64_t ObjectAttributeAddress; + FDP_ReadVirtualMemory(pFDP, 0, (uint8_t*)&ObjectAttributeAddress, 8, R8Value + (2 * 8)); + + uint64_t ObjectNameAddress; + FDP_ReadVirtualMemory(pFDP, 0, (uint8_t*)&ObjectNameAddress, 8, ObjectAttributeAddress + 8); + + wchar_t ObjectName[512]; + FDP_ReadVirtualMemory(pFDP, 0, (uint8_t*)ObjectName, 512, ObjectNameAddress); + ObjectName[254] = 0x00; + printf("%S\n", ObjectName); + + if (FDP_WriteRegister(pFDP, 0, FDP_DR7_REGISTER, 0) == false){ + printf("Failed to write DR7!\n"); + return false; + } + if (FDP_SingleStep(pFDP, 0) == false){ + return false; + } + if (FDP_WriteRegister(pFDP, 0, FDP_DR7_REGISTER, 0x0000000000000403) == false){ + printf("Failed to write DR7!\n"); + return false; + } + if (FDP_Resume(pFDP) == false){ + return false; + } + } + } + } + printTime(); + + if (FDP_WriteRegister(pFDP, 0, FDP_DR0_REGISTER, oldDR0Value) == false){ + return false; + } + + if (FDP_WriteRegister(pFDP, 0, FDP_DR7_REGISTER, oldDR7Value) == false){ + return false; + } + + if (FDP_Resume(pFDP) == false){ + return false; + } + + printf("[OK]\n"); + return true; +}*/ + +bool testSetCr3(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + uint64_t u64OldCr3; + uint64_t OldValue; + uint64_t Value; + + FDP_Pause(pFDP); + FDP_ReadRegister(pFDP, 0, FDP_CR3_REGISTER, &u64OldCr3); + FDP_ReadVirtualMemory(pFDP, 0, (uint8_t*)&OldValue, sizeof(Value), 0xFFFFF6FB7DBEDF68); + + FDP_WriteRegister(pFDP, 0, FDP_CR3_REGISTER, u64OldCr3 + 0x1000); + FDP_ReadVirtualMemory(pFDP, 0, (uint8_t*)&Value, sizeof(Value), 0xFFFFF6FB7DBEDF68); + if (Value == OldValue){ + printf("Failed to change Cr3 !\n"); + return false; + } + + FDP_WriteRegister(pFDP, 0, FDP_CR3_REGISTER, u64OldCr3); + FDP_ReadVirtualMemory(pFDP, 0, (uint8_t*)&Value, sizeof(Value), 0xFFFFF6FB7DBEDF68); + if (Value != OldValue){ + printf("Failed to restore Cr3 !"); + return false; + } + + FDP_Resume(pFDP); + + printf("[OK]\n"); + return true; +} + + +bool testSingleStepPageBreakpoint(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + bool bReturnValue = false; + + //Bug single-step PageHyperBreakpoint + FDP_Pause(pFDP); + + uint64_t originalMSRValue; + if (FDP_ReadMsr(pFDP, 0, MSR_LSTAR, &originalMSRValue) == false) { + printf("Failed to read MSRValue !\n"); + return false; + } + + int BreakpointId = FDP_SetBreakpoint(pFDP, 0, FDP_PAGEHBP, -1, FDP_EXECUTE_BP, FDP_VIRTUAL_ADDRESS, originalMSRValue, 1, FDP_NO_CR3); + if (BreakpointId < 0) { + printf("Failed to insert page breakpoint !\n"); + return false; + } + + if (FDP_Resume(pFDP) == false) { + printf("Failed to resume !\n"); + return false; + } + + bool bRunning = true; + while (bRunning == true) { + if (FDP_GetStateChanged(pFDP)) { + FDP_State state; + if (FDP_GetState(pFDP, &state) == false) { + printf("Failed to get state !\n"); + return false; + } + if (state & FDP_STATE_PAUSED + && state & FDP_STATE_BREAKPOINT_HIT + && !(state & FDP_STATE_DEBUGGER_ALERTED)) { + break; + } + } + } + + uint64_t OldRip; + FDP_ReadRegister(pFDP, 0, FDP_RIP_REGISTER, &OldRip); + FDP_SingleStep(pFDP, 0); + uint64_t NewRip; + FDP_ReadRegister(pFDP, 0, FDP_RIP_REGISTER, &NewRip); + if (OldRip == NewRip) { + bReturnValue = false; + } + else { + bReturnValue = true; + } + + FDP_Pause(pFDP); + FDP_UnsetBreakpoint(pFDP, BreakpointId); + FDP_Resume(pFDP); + if (bReturnValue == true) { + printf("[OK]\n"); + } + else { + printf("[FAIL]\n"); + } + return bReturnValue; +} + +bool testSingleStepPause(FDP_SHM* pFDP) +{ + printf("%s ...", __FUNCTION__); + + FDP_Resume(pFDP); + FDP_SingleStep(pFDP, 0); + printf("[OK]\n"); + + return true; +} + + + + +int testFDP(char *pVMName) { + bool bReturnCode = false; + FDP_SHM* pFDP = FDP_OpenSHM(pVMName); + if (pFDP) { + //Start Timer Thread + // CreateThread(NULL, 0, TimerRoutine, NULL, 0, NULL); + pthread_t t1; + pthread_create(&t1, NULL, TimerRoutine, NULL); + + if (FDP_Init(pFDP) == false) { + printf("Failed to FDP_Init !\n"); + goto Fail; + } + + if (testUnsetBreakpoint(pFDP) == false) + goto Fail; + if (testSingleStepPause(pFDP) == false) + goto Fail; + if (testSingleStepPageBreakpoint(pFDP) == false) + goto Fail; + if (testSingleStepSpeed(pFDP) == false) + goto Fail; + if (testReadWriteMSR(pFDP) == false) + goto Fail; + if (testSetCr3(pFDP) == false) + goto Fail; + if (testMultiThread(pFDP) == false) + goto Fail; + if (testState(pFDP) == false) + goto Fail; + if (FDP_Pause(pFDP) == false) + goto Fail; + if (testVirtualSyscallBP(pFDP, FDP_SOFTHBP) == false) + goto Fail; + if (testMultiCpu(pFDP) == false) + goto Fail; + if (testReadWriteRegister(pFDP) == false) + goto Fail; + if (testReadWritePhysicalMemory(pFDP) == false) + goto Fail; + if (testReadWriteVirtualMemory(pFDP) == false) + goto Fail; + if (testGetStatePerformance(pFDP) == false) + goto Fail; + if (testDebugRegisters(pFDP) == false) + goto Fail; + if (testVirtualSyscallBP(pFDP, FDP_PAGEHBP) == false) + goto Fail; + if (testVirtualSyscallBP(pFDP, FDP_SOFTHBP) == false) + goto Fail; + if (testPhysicalSyscallBP(pFDP, FDP_PAGEHBP) == false) + goto Fail; + if (testPhysicalSyscallBP(pFDP, FDP_SOFTHBP) == false) + goto Fail; + if (testMultipleVirtualSyscallBP(pFDP, FDP_PAGEHBP) == false) + goto Fail; + if (testMultipleVirtualSyscallBP(pFDP, FDP_SOFTHBP) == false) + goto Fail; + if (testMultiplePhysicalSyscallBP(pFDP, FDP_PAGEHBP) == false) + goto Fail; + if (testMultiplePhysicalSyscallBP(pFDP, FDP_SOFTHBP) == false) + goto Fail; + if (testReadAllPhysicalMemory(pFDP) == false) + goto Fail; + if (testReadWriteAllPhysicalMemory(pFDP) == false) + goto Fail; + if (testLargeVirtualPageSyscallBP(pFDP) == false) + goto Fail; + if (testLargePhysicalPageSyscallBP(pFDP) == false) + goto Fail; + if (testReadWriteVirtualMemorySpeed(pFDP) == false) + goto Fail; + if (testReadWritePhysicalMemorySpeed(pFDP) == false) + goto Fail; + if (testReadLargePhysicalMemory(pFDP) == false) + goto Fail; + if (testReadLargeVirtualMemory(pFDP) == false) + goto Fail; + /*if (testSaveRestore(pFDP) == false) + goto Fail;*/ + + bReturnCode = true; + Fail: + testUnsetBreakpoint(pFDP); + FDP_Resume(pFDP); + } + + printf("*********************\n"); + printf("*********************\n"); + if (bReturnCode == false){ + printf("** TESTS FAILED ! **\n"); + } + else{ + printf("** TESTS PASSED ! **\n"); + } + printf("*********************\n"); + printf("*********************\n"); + return bReturnCode; +} + + +void usage(char * binPath){ + printf("Usage: %s [VM Name]\n", binPath); +} + +int main(int argc, char *argv[]){ + if (argc != 2){ + usage(argv[0]); + return 2; + } + + if (testFDP(argv[1])) + return 0; + else + return 1; +} diff --git a/FDPutils/TestFDP/testFDPClientServer.c b/FDPutils/TestFDP/testFDPClientServer.c new file mode 100755 index 00000000..c62223cf --- /dev/null +++ b/FDPutils/TestFDP/testFDPClientServer.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "FDP.h" +#include "FDP_structs.h" + + + +//TODO ! Unit Tests + +volatile uint32_t count_per_sec = 0; +volatile bool bIsRunning = true; + +bool FDP_DummyReadRegister(void* pUserHandle, uint32_t u32CpuId, FDP_Register u8RegisterId, uint64_t* pRegisterValue) +{ + *pRegisterValue = count_per_sec; + count_per_sec = 0; + return true; +} + + +bool FDP_DummyWriteRegister(void* pUserHandle, uint32_t u32CpuId, FDP_Register u8RegisterId, uint64_t pRegisterValue) +{ + //uint16_t t = rand(); + //for(int i = 0; ipFdpServer->bIsRunning == false) + { + printf("."); + } + + while(bIsRunning){ + FDP_WriteRegister(pFDPClient, 0, FDP_CS_REGISTER, 0xCAFECAFECAFECAFE); + } + return NULL; +} + + +void* counter_core(void* lpParameter) +{ + FDP_SHM* pFDPClient = (FDP_SHM*)lpParameter; + uint64_t count = 0; + for(int i=0; i<10; i++){ + printf("...\n"); + sleep(1); + printf("Read...\n"); + FDP_ReadRegister(pFDPClient, 0, FDP_CS_REGISTER, &count); + printf("%llu/sec\n", count); + } + bIsRunning = false; + exit(0); + return NULL; +} + +bool FDP_ClientServerTest() +{ + //Building FDP Server Interface + FDP_SERVER_INTERFACE_T FDPServerInterface; + //FDPServerInterface.bIsRunning = true; + FDPServerInterface.pUserHandle = NULL; + FDPServerInterface.pfnReadRegister = FDP_DummyReadRegister; + FDPServerInterface.pfnWriteRegister = FDP_DummyWriteRegister; + FDPServerInterface.pfnGetCpuCount = FDP_DummyGetCpuCount; + FDP_SHM* pFDPServer = FDP_CreateSHM("FDP_TEST"); + + if (pFDPServer == NULL) + { + printf("Failed to FDP_CreateSHM\n"); + return false; + } + if (FDP_SetFDPServer(pFDPServer, &FDPServerInterface) == false) + { + printf("Failed to FDP_SerFDPServer\n"); + return false; + } + + /*//Create a fake Client... + HANDLE hThreadServer = INVALID_HANDLE_VALUE; + hThreadServer = CreateThread(NULL, 0, FDP_UnitTestClient, pFDPServer, 0, 0); + if (hThreadServer == INVALID_HANDLE_VALUE) + { + printf("Failed to CreateThread\n"); + return false; + }*/ + pthread_t threadServer = 0; + for(int i=0; i<1; i++){ + if(pthread_create(&threadServer, NULL, FDP_UnitTestClient, pFDPServer) != 0){ + printf("Failed to phread_create\n"); + return false; + } + } + + pthread_t threadCounter = 0; + if(pthread_create(&threadCounter, NULL, counter_core, pFDPServer) != 0){ + printf("Failed to phread_create\n"); + return -1; + } + + + if (FDP_ServerLoop(pFDPServer) == false) + { + printf("Failed to FDP_ServerLoop\n"); + return false; + } + Clean: + //Closing server + FDPServerInterface.bIsRunning = false; + //WaitForSingleObject(hThreadServer, INFINITE); + pthread_join(threadServer, NULL); + return true; +} + + +int main(int argc, char* argv[]) +{ + FDP_ClientServerTest(); + return 0; +} diff --git a/FDPutils/TestFDP/utils.h b/FDPutils/TestFDP/utils.h new file mode 100644 index 00000000..c92b337e --- /dev/null +++ b/FDPutils/TestFDP/utils.h @@ -0,0 +1,101 @@ +/* + MIT License + + Copyright (c) 2015 Nicolas Couffin ncouffin@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX(a,b) (((a)>(b))?(a):(b)) + +//define if you don't want 100% CPU consomption, performance will be lower... +#define WINBAGILITY_POWER_SAVE 1 + +//define verbose log level +#define DEBUG_LEVEL 0 +#define DEBUG_FLOW 0 + +#if DEBUG_LEVEL > 0 +#define Log1(fmt,...) printf(fmt, ##__VA_ARGS__) +#else +#define Log1(fmt,...) +#endif + +#if DEBUG_FLOW > 0 +#define LogFlow() printf("%s\n", __FUNCTION__); +#else +#define LogFlow() +#endif + +#define MSR_EFER 0xC0000080 +#define MSR_STAR 0xC0000081 +#define MSR_LSTAR 0xC0000082 +#define MSR_CSTAR 0xC0000084 +#define MSR_SYSCALL_MASK 0xC0000084 +#define MSR_GS_BASE 0xC0000101 +#define MSR_KERNEL_GS_BASE 0xC0000102 + +#define _1G 1*1024*1024*1024 +#ifndef _1M +#define _1M 1024*1024 +#endif +#define _2M 2*_1M +#define _4K 4096 + +#define KERNEL_SPACE_START 0xFFFFF80000000000 +#define KERNEL_SPACE_START_X86 0x80000000 + +void printTime(); +int roundup16(int value); +void dumpHexData(char *tmp, int len); +void printHexData(char *tmp, int len); + +// //todo: NamedPipe utilities file +// bool CreateConnectNamedPipe(HANDLE* hPipe, char* pipeName); +// bool OpenNamedPipe(HANDLE* hPipe, char* pipeName); +// bool GetPipeTry(HANDLE hPipe, uint8_t* data, uint64_t size, bool timeout); +// bool GetPipe(HANDLE hPipe, uint8_t* data, uint64_t size); +// uint8_t Get8Pipe(HANDLE hPipe); +// uint16_t Get16Pipe(HANDLE hPipe); +// uint32_t Get32Pipe(HANDLE hPipe); +// uint64_t Get64Pipe(HANDLE hPipe); +// DWORD PutPipe(HANDLE hPipe, uint8_t* data, uint64_t size); +// DWORD Put8Pipe(HANDLE hPipe, uint8_t data); +// DWORD Put32Pipe(HANDLE hPipe, uint32_t data); +// DWORD Put64Pipe(HANDLE hPipe, uint64_t data); + + +// //TODO: ... +// __inline uint64_t LeftShift(uint64_t value, uint64_t count) +// { +// for (int i = 0; i < count; i++){ +// value = value << 1; +// } +// return value; +// } + + +// bool IsLetter(char c); +// char* GetSymbolsDirectory(); +// bool ReccurseFile(char *pDirectoryPath, bool(*pfnUserCallBack)(void *pUserHandle, char *pFilePath), void *pUserHandle, bool bBreakOnTrue); + +#endif //__UTILS_H__ diff --git a/FDPutils/VirtualBox-5.2.14_FDP_macOS.patch b/FDPutils/VirtualBox-5.2.14_FDP_macOS.patch new file mode 100644 index 00000000..525a6f41 --- /dev/null +++ b/FDPutils/VirtualBox-5.2.14_FDP_macOS.patch @@ -0,0 +1,3994 @@ +diff --git a/Config.kmk b/Config.kmk +index 5143b164..73a5a76b 100644 +--- a/Config.kmk ++++ b/Config.kmk +@@ -1589,9 +1589,9 @@ endif + ifdef VBOX_STRICT + DEFS += VBOX_STRICT + endif +-ifdef LOG_ENABLED ++#ifdef LOG_ENABLED + DEFS += LOG_ENABLED +-endif ++#endif + ifdef VBOX_OSE + DEFS += VBOX_OSE + endif +@@ -1606,9 +1606,9 @@ ifdef VBOX_WITH_RAW_MODE + endif + + # Don't flood CDEFS, old MASMs doesn't like too many defines. +-ifdef VBOX_WITH_DEBUGGER ++#ifdef VBOX_WITH_DEBUGGER + CDEFS += VBOX_WITH_DEBUGGER +-endif ++#endif + ifdef VBOX_WITH_HARDENING + CDEFS += VBOX_WITH_HARDENING + endif +@@ -2215,8 +2215,12 @@ ifeq ($(KBUILD_TARGET),darwin) + endif + endif + ifdef VBOX_WITH_NEW_XCODE ++ if $(KBUILD_HOST_VERSION_MAJOR) >= 18 # HACK ALERT! dtrace doesn't work on Mojave if we specify our Xcode 6.2 environment. ++ override TOOL_StandardDTrace_DTRACE := dtrace "-xcpppath=$(VBOX_PATH_MACOSX_DEVEL_ROOT)/usr/bin/gcc" ++ else + override TOOL_StandardDTrace_DTRACE := $(TOOL_$(VBOX_GCC_TOOL)_ENV_SETUP) dtrace "-xcpppath=$(VBOX_PATH_MACOSX_DEVEL_ROOT)/usr/bin/gcc" +- export PATH:=$(VBOX_PATH_MACOSX_SDK)/usr/bin:$(PATH) ++ endif ++ export PATH:=$(VBOX_PATH_MACOSX_SDK)/usr/bin:$(PATH) + else + if $(VBOX_XCODE_VERSION_MAJOR) > 4 || ($(VBOX_XCODE_VERSION_MAJOR) == 4 && $(VBOX_XCODE_VERSION_MINOR) >= 2) + TOOL_GXX4MACHO_PREFIX ?= llvm- +diff --git a/configure b/configure +index ca59623e..a6e37960 100755 +--- a/configure ++++ b/configure +@@ -153,7 +153,7 @@ INCVPX="" + LIBVPX="-lvpx" + PKGCONFIG="`which_wrapper pkg-config`" + PYTHONDIR="/usr /usr/local" +-QT5DIR="/usr/lib/qt5 /usr/share/qt5 /usr/lib64/qt5 /usr /usr/local" ++QT5DIR="/usr/lib/qt5 /usr/share/qt5 /usr/lib64/qt5 /usr /usr/local /opt/local/libexec/qt5" + QT5DIR_PKGCONFIG=1 + QT5MAJ=5 + QT5MIN=6 +@@ -1510,7 +1510,7 @@ EOF + # Now try the user provided directory and some of the standard directories. + QT_TRIES="$QT5DIR /System/Library /Library" + for t in $QT_TRIES; do +- if [ -f "$t/Frameworks/QtCore.framework/QtCore" ]; then ++ if [ -f "$t/lib/QtCore.framework/QtCore" ]; then + PATH_SDK_QT5="$t" + break + fi +@@ -1518,8 +1518,8 @@ EOF + # Add the necessary params for building the test application + if [ -n "$PATH_SDK_QT5" ]; then + foundqt5=1 +- INCQT5=-I$PATH_SDK_QT5/Frameworks/QtCore.framework/Headers +- LIBQT5=-F$PATH_SDK_QT5/Frameworks ++ INCQT5=-I$PATH_SDK_QT5/lib/QtCore.framework/Headers ++ LIBQT5=-F$PATH_SDK_QT5/lib + FLGQT5="-framework QtCore" + else + log_failure "Qt5 framework not found (can be disabled using --disable-qt)" +@@ -1600,9 +1600,9 @@ EOF + if [ "$OS" = "darwin" ]; then + # Successful build & run the test application so add the necessary + # params to AutoConfig.kmk: +- cnf_append "PATH_SDK_QT5_INC" "$PATH_SDK_QT5/Frameworks" +- cnf_append "PATH_SDK_QT5_LIB" "$PATH_SDK_QT5/Frameworks" +- cnf_append "PATH_SDK_QT5" "$PATH_SDK_QT5/Frameworks" ++ cnf_append "PATH_SDK_QT5_INC" "$PATH_SDK_QT5/inc" ++ cnf_append "PATH_SDK_QT5_LIB" "$PATH_SDK_QT5/lib" ++ cnf_append "PATH_SDK_QT5" "$PATH_SDK_QT5" + # Check for the moc tool in the Qt directory found & some standard + # directories. + for q in $PATH_SDK_QT5 /usr /Developer/Tools/Qt; do +@@ -2167,6 +2167,14 @@ check_darwinversion() + test_header "Darwin version" + darwin_ver=`uname -r` + case "$darwin_ver" in ++ 18\.*) ++ check_xcode_sdk_path "$WITH_XCODE_DIR" ++ [ $? -eq 1 ] || fail ++ darwin_ver="10.14" # Mojave ++ sdk=$WITH_XCODE_DIR/Developer/SDKs/MacOSX10.6.sdk ++ cnf_append "VBOX_WITH_MACOSX_COMPILERS_FROM_DEVEL" "1" ++ cnf_append "VBOX_PATH_MACOSX_DEVEL_ROOT" "$WITH_XCODE_DIR/Developer" ++ ;; + 17\.*) + check_xcode_sdk_path "$WITH_XCODE_DIR" + [ $? -eq 1 ] || fail +diff --git a/include/VBox/vmm/em.h b/include/VBox/vmm/em.h +index 254a5a25..da3f9278 100644 +--- a/include/VBox/vmm/em.h ++++ b/include/VBox/vmm/em.h +@@ -288,6 +288,11 @@ VMMR3_INT_DECL(int) EMR3NotifyResume(PVM pVM); + VMMR3_INT_DECL(int) EMR3NotifySuspend(PVM pVM); + VMMR3_INT_DECL(VBOXSTRICTRC) EMR3HmSingleInstruction(PVM pVM, PVMCPU pVCpu, uint32_t fFlags); + ++/*MYCODE*/ ++VMMR3_INT_DECL(void) EMR3ResetU(PUVM pUVM); ++VMMR3_INT_DECL(int) EMR3ProcessForcedAction(PVM pVM, PVMCPU pVCpu, int rc); ++/*ENDMYCODE*/ ++ + /** @} */ + #endif /* IN_RING3 */ + +diff --git a/include/VBox/vmm/gmm.h b/include/VBox/vmm/gmm.h +index df50d0a7..c15c390c 100644 +--- a/include/VBox/vmm/gmm.h ++++ b/include/VBox/vmm/gmm.h +@@ -545,6 +545,17 @@ typedef GMMMEMSTATSREQ *PGMMMEMSTATSREQ; + GMMR0DECL(int) GMMR0QueryHypervisorMemoryStatsReq(PGMMMEMSTATSREQ pReq); + GMMR0DECL(int) GMMR0QueryMemoryStatsReq(PGVM pGVM, PVM pVM, VMCPUID idCpu, PGMMMEMSTATSREQ pReq); + ++/*MYCODE*/ ++typedef struct ALLOCPAGEREQ ++{ ++ SUPVMMR0REQHDR Hdr; ++ uint64_t newPageSize; ++ uint64_t newPageHCPHys; ++ uint8_t* newPageR3Ptr; ++} ALLOCPAGEREQ; ++/*ENDMYCODE*/ ++ ++ + /** + * Request buffer for GMMR0MapUnmapChunkReq / VMMR0_DO_GMM_MAP_UNMAP_CHUNK. + * @see GMMR0MapUnmapChunk +diff --git a/include/VBox/vmm/hm.h b/include/VBox/vmm/hm.h +index 974742ba..469e27f9 100644 +--- a/include/VBox/vmm/hm.h ++++ b/include/VBox/vmm/hm.h +@@ -173,6 +173,9 @@ VMM_INT_DECL(void) HMSvmNstGstVmExitNotify(PVMCPU pVCpu, PCPUMCTX p + #ifndef IN_RC + VMM_INT_DECL(int) HMFlushTLB(PVMCPU pVCpu); + VMM_INT_DECL(int) HMFlushTLBOnAllVCpus(PVM pVM); ++/*MYCODE*/ ++VMM_INT_DECL(int) HMFlushTLBOnAllVCpus2(PVM pVM); ++/*ENCODE*/ + VMM_INT_DECL(int) HMInvalidatePageOnAllVCpus(PVM pVM, RTGCPTR GCVirt); + VMM_INT_DECL(int) HMInvalidatePhysPage(PVM pVM, RTGCPHYS GCPhys); + VMM_INT_DECL(bool) HMIsNestedPagingActive(PVM pVM); +@@ -194,6 +197,7 @@ VMM_INT_DECL(PGMMODE) HMGetShwPagingMode(PVM pVM); + * @{ + */ + VMMR0_INT_DECL(int) HMR0Init(void); ++VMMR0_INT_DECL(int) HMR0FlushEPT(PVM pVM, PVMCPU pVCpu); + VMMR0_INT_DECL(int) HMR0Term(void); + VMMR0_INT_DECL(int) HMR0InitVM(PVM pVM); + VMMR0_INT_DECL(int) HMR0TermVM(PVM pVM); +diff --git a/include/VBox/vmm/mm.h b/include/VBox/vmm/mm.h +index 685d0cb7..651bb6cd 100644 +--- a/include/VBox/vmm/mm.h ++++ b/include/VBox/vmm/mm.h +@@ -228,6 +228,7 @@ VMMDECL(bool) MMHyperIsInsideArea(PVM pVM, RTGCPTR GCPtr); + + VMMDECL(RTHCPHYS) MMPage2Phys(PVM pVM, void *pvPage); + VMMDECL(void *) MMPagePhys2Page(PVM pVM, RTHCPHYS HCPhysPage); ++VMMDECL(void *) MMPagePhys2PageU(PUVM pUVM, RTHCPHYS HCPhysPage); + VMMDECL(int) MMPagePhys2PageEx(PVM pVM, RTHCPHYS HCPhysPage, void **ppvPage); + VMMDECL(int) MMPagePhys2PageTry(PVM pVM, RTHCPHYS HCPhysPage, void **ppvPage); + +@@ -295,6 +296,7 @@ VMMR3DECL(int) MMR3HyperReadGCVirt(PVM pVM, void *pvDst, RTGCPTR GCPtr, siz + * @todo retire this group, elimintating or moving MMR3PhysGetRamSize to PGMPhys. + * @{ */ + VMMR3DECL(uint64_t) MMR3PhysGetRamSize(PVM pVM); ++VMMR3DECL(uint64_t) MMR3PhysGetRamSizeU(PUVM pUVM); + VMMR3DECL(uint32_t) MMR3PhysGetRamSizeBelow4GB(PVM pVM); + VMMR3DECL(uint64_t) MMR3PhysGetRamSizeAbove4GB(PVM pVM); + VMMR3DECL(uint32_t) MMR3PhysGet4GBRamHoleSize(PVM pVM); +diff --git a/include/VBox/vmm/pgm.h b/include/VBox/vmm/pgm.h +index 92edd91a..70247db2 100644 +--- a/include/VBox/vmm/pgm.h ++++ b/include/VBox/vmm/pgm.h +@@ -421,6 +421,24 @@ VMMDECL(int) PGMShwMakePageNotPresent(PVMCPU pVCpu, RTGCPTR GCPtr, ui + /** The page is an MMIO2. */ + #define PGM_MK_PG_IS_MMIO2 RT_BIT(1) + /** @}*/ ++ ++ ++/*MYCODE*/ ++VMMDECL(int) PGMShwGetHCPage(PVMCPU pVCpu, uint64_t GCPhys, uint64_t *HCPhys); ++VMMDECL(int) PGMShwSetHCPage(PVMCPU pVCpu, uint64_t GCPhys, uint64_t HCPhys); ++VMMDECL(int) PGMShwSaveRights(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwRestoreRights(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwPresent(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwNoPresent(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwWrite(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwNoWrite(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwExecute(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwNoExecute(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwInvalidate(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwSetBreakable(PVMCPU pVCpu, uint64_t GCPhys, bool Breakable); ++VMMDECL(bool) PGMShwIsBreakable(PVMCPU pVCpu, uint64_t GCPhys); ++/*ENDMYCODE*/ ++ + VMMDECL(int) PGMGstGetPage(PVMCPU pVCpu, RTGCPTR GCPtr, uint64_t *pfFlags, PRTGCPHYS pGCPhys); + VMMDECL(bool) PGMGstIsPagePresent(PVMCPU pVCpu, RTGCPTR GCPtr); + VMMDECL(int) PGMGstSetPage(PVMCPU pVCpu, RTGCPTR GCPtr, size_t cb, uint64_t fFlags); +@@ -629,6 +647,7 @@ VMMDECL(VBOXSTRICTRC) PGMPhysReadGCPtr(PVMCPU pVCpu, void *pvDst, RTGCPTR GCPtrS + VMMDECL(VBOXSTRICTRC) PGMPhysWriteGCPtr(PVMCPU pVCpu, RTGCPTR GCPtrDst, const void *pvSrc, size_t cb, PGMACCESSORIGIN enmOrigin); + + VMMDECL(int) PGMPhysSimpleReadGCPhys(PVM pVM, void *pvDst, RTGCPHYS GCPhysSrc, size_t cb); ++VMMDECL(int) PGMPhysSimpleReadGCPhys2(PUVM pUVM, void *pvDst, RTGCPHYS GCPhysSrc, size_t cb); + VMMDECL(int) PGMPhysSimpleWriteGCPhys(PVM pVM, RTGCPHYS GCPhysDst, const void *pvSrc, size_t cb); + VMMDECL(int) PGMPhysSimpleReadGCPtr(PVMCPU pVCpu, void *pvDst, RTGCPTR GCPtrSrc, size_t cb); + VMMDECL(int) PGMPhysSimpleWriteGCPtr(PVMCPU pVCpu, RTGCPTR GCPtrDst, const void *pvSrc, size_t cb); +@@ -698,6 +717,7 @@ VMMR0_INT_DECL(int) PGMR0PhysSetupIoMmu(PGVM pGVM, PVM pVM); + VMMR0DECL(int) PGMR0SharedModuleCheck(PVM pVM, PGVM pGVM, VMCPUID idCpu, PGMMSHAREDMODULE pModule, PCRTGCPTR64 paRegionsGCPtrs); + VMMR0DECL(int) PGMR0Trap0eHandlerNestedPaging(PVM pVM, PVMCPU pVCpu, PGMMODE enmShwPagingMode, RTGCUINT uErr, PCPUMCTXCORE pRegFrame, RTGCPHYS pvFault); + VMMR0DECL(VBOXSTRICTRC) PGMR0Trap0eHandlerNPMisconfig(PVM pVM, PVMCPU pVCpu, PGMMODE enmShwPagingMode, PCPUMCTXCORE pRegFrame, RTGCPHYS GCPhysFault, uint32_t uErr); ++VMMR0DECL(VBOXSTRICTRC) PGMR0PhysSimpleReadGCPhys(PVM pVM, void *pvDst, RTGCPHYS GCPhysSrc, size_t cb); + # ifdef VBOX_WITH_2X_4GB_ADDR_SPACE + VMMR0DECL(int) PGMR0DynMapInit(void); + VMMR0DECL(void) PGMR0DynMapTerm(void); +@@ -728,6 +748,7 @@ VMMR3_INT_DECL(void) PGMR3MemSetup(PVM pVM, bool fReset); + VMMR3DECL(int) PGMR3Term(PVM pVM); + VMMR3DECL(int) PGMR3LockCall(PVM pVM); + VMMR3DECL(int) PGMR3ChangeMode(PVM pVM, PVMCPU pVCpu, PGMMODE enmGuestMode); ++VMMR3DECL(int) PGMR3ChangeMode2(PUVM pUVM, PVMCPU pVCpu, PGMMODE enmGuestMode); + + VMMR3DECL(int) PGMR3PhysRegisterRam(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, const char *pszDesc); + VMMR3DECL(int) PGMR3PhysChangeMemBalloon(PVM pVM, bool fInflate, unsigned cPages, RTGCPHYS *paPhysPage); +@@ -832,6 +853,10 @@ VMMR3DECL(int) PGMR3PhysGCPhys2CCPtrReadOnlyExternal(PVM pVM, RTGCPHYS GCPh + VMMR3DECL(int) PGMR3PhysChunkMap(PVM pVM, uint32_t idChunk); + VMMR3DECL(void) PGMR3PhysChunkInvalidateTLB(PVM pVM); + VMMR3DECL(int) PGMR3PhysAllocateHandyPages(PVM pVM); ++/*MYCODE*/ ++VMMR3DECL(int) PGMR3PhysAllocateLargeHandyPage2(PVM pVM); ++VMMR3_INT_DECL(int) PGMR3DbgScanPhysicalU(PUVM pUVM, RTGCPHYS GCPhys, RTGCPHYS cbRange, RTGCPHYS GCPhysAlign, const uint8_t *pabNeedle, size_t cbNeedle, PRTGCPHYS pGCPhysHit); ++/*ENDMYCODE*/ + VMMR3DECL(int) PGMR3PhysAllocateLargeHandyPage(PVM pVM, RTGCPHYS GCPhys); + + VMMR3DECL(int) PGMR3CheckIntegrity(PVM pVM); +diff --git a/include/VBox/vmm/vm.h b/include/VBox/vmm/vm.h +index 9e4e6de2..45203960 100644 +--- a/include/VBox/vmm/vm.h ++++ b/include/VBox/vmm/vm.h +@@ -47,6 +47,61 @@ + * @{ + */ + ++/*MYCODE*/ ++typedef struct HardwarePage_t{ ++ int ReferenceCount; //Number of breakpoint using this page ++ uint64_t PageSize; //Size of the page ++ uint64_t HCPhys; //Host-Context physical address of the page ++ uint8_t* R3Ptr; //Ring-3 virtual address of the page ++}HardwarePage_t; ++ ++typedef struct GCPhysArea_t{ ++ uint64_t Start; ++ uint64_t End; ++}GCPhysArea_t; ++ ++typedef struct PfnEntrie_t{ ++ uint8_t n; ++ struct{ ++ bool u1Present; ++ bool u1Write; ++ bool u1Execute; ++ bool u1Breakable; ++ }u; ++}PfnEntrie_t; ++ ++typedef struct BreakpointEntrie_t{ ++ //Is the breakpoint activated or free ++ bool breakpointActivated; ++ //Tag the breakpoint to know if the breakpoint changed... ++ uint64_t breakpointTag; ++ //Kind of breakpoint PAGEHBP, HARDHBP, SOFTHBP ++ uint8_t breakpointType; ++ //Guest virtual address of the breakpoint or start of the breakpoint ++ uint64_t breakpointGCPtr; ++ //Guest physical address of the breakpoint or start of the breakpoint ++ uint64_t breakpointGCPhys; ++ //Lengt of the breakpoint (PAGEHBP only) ++ uint64_t breakpointLength; ++ //EXECUTE_BP, READ_BP, WRITE_BP ++ uint8_t breakpointAccessType; ++ //Size of the page where the breakpoint is ++ uint64_t breakpointPageSize; ++ //Original host physical page ++ uint64_t breakpointOrigHCPhys; ++ //Original opcode byte ++ uint8_t breakpointOriginalByte; ++ //Pointer to HardwarePage ++ HardwarePage_t* breakpointHardwarePage; ++ uint64_t breakpointGCPhysAreaCount; ++ GCPhysArea_t* breakpointGCPhysAreaTable; ++ //Condition ++ uint64_t breakpointCr3; ++}BreakpointEntrie_t; ++ ++#define MAX_BREAKPOINT_ID 255 ++/*ENDMYCODE*/ ++ + /** + * The state of a Virtual CPU. + * +@@ -149,6 +204,34 @@ typedef struct VMCPU + uint8_t padding[18496]; /* multiple of 64 */ + } iem; + ++ /*MYCODE*/ ++ union ++ { ++ struct{ ++ volatile uint8_t u8StateBitmap; ++ volatile bool bSingleStepRequired; ++ volatile bool bPauseRequired; ++ volatile bool bDisableInterrupt; ++ volatile bool bRebootRequired; ++ volatile bool bSuspendRequired; ++ volatile bool bRestoreRequired; ++ volatile bool bPageFaultOverflowGuard; ++ ++ volatile bool bHardHyperBreakPointHitted; ++ volatile bool bPageHyperBreakPointHitted; ++ volatile bool bSoftHyperBreakPointHitted; ++ volatile bool bMsrHyperBreakPointHitted; ++ volatile bool bCrHyperBreakPointHitted; ++ volatile bool bInstallDrBreakpointRequired; ++ //Fake Debug registers to keep "legit-guest" values ++ uint64_t aGuestDr[8]; ++ volatile uint64_t u64TickCount; ++ void* pCpuShm; ++ }s; ++ uint8_t padding[4096]; /* multiple of 4096 */ ++ } mystate; ++ /*ENDMYCODE*/ ++ + /** HM part. */ + union VMCPUUNIONHM + { +@@ -278,6 +361,7 @@ typedef struct VMCPU + #endif + uint8_t padding[4096]; /* multiple of 4096 */ + } cpum; ++ + } VMCPU; + + +@@ -1110,6 +1194,28 @@ typedef struct VM + uint8_t padding[1600]; /* multiple of 64 */ + } vmm; + ++ /*MYCODE*/ ++ union{ ++ BreakpointEntrie_t l[MAX_BREAKPOINT_ID+1]; ++ uint8_t padding[4096*32]; /* Must be page aligned ! */ ++ }bp; ++ ++ union{ ++ struct{ ++ void *pFdpShm; ++ uint32_t u32HardwarePageTableCount; ++ HardwarePage_t aHardwarePageTable[64]; ++ volatile uint8_t u8StateBitmap; ++ char PageSpinLockName[256]; ++ RTSPINLOCK PageSpinlock; ++ PfnEntrie_t *pPfnTableR3; ++ PfnEntrie_t *pPfnTableR0; ++ RTSPINLOCK CpuLock; ++ }s; ++ uint8_t padding[4096]; /* Must be page aligned ! */ ++ }mystate; ++ /*ENDMYCODE*/ ++ + /** PGM part. */ + union + { +@@ -1119,6 +1225,7 @@ typedef struct VM + uint8_t padding[4096*2+6080]; /* multiple of 64 */ + } pgm; + ++ + /** HM part. */ + union + { +@@ -1329,6 +1436,7 @@ typedef struct VM + * Must be aligned on a page boundary for TLB hit reasons as well as + * alignment of VMCPU members. */ + VMCPU aCpus[1]; ++ + } VM; + + +diff --git a/include/VBox/vmm/vm.mac b/include/VBox/vmm/vm.mac +index da1fa31b..f3302c99 100644 +--- a/include/VBox/vmm/vm.mac ++++ b/include/VBox/vmm/vm.mac +@@ -61,6 +61,7 @@ struc VMCPU + + alignb 64 + .iem resb 18496 ++ .mystate resb 4096 + .hm resb 5824 + .em resb 1408 + .trpm resb 128 +@@ -151,6 +152,8 @@ struc VM + alignb 64 + .cpum resb 1536 + .vmm resb 1600 ++ .bp resb (4096*32) ++ .mystate resb 4096 + .pgm resb (4096*2+6080) + .hm resb 5440 + .trpm resb 5248 +diff --git a/include/VBox/vmm/vmapi.h b/include/VBox/vmm/vmapi.h +index cd5fb2c2..6dad9610 100644 +--- a/include/VBox/vmm/vmapi.h ++++ b/include/VBox/vmm/vmapi.h +@@ -478,6 +478,32 @@ VMMR3_INT_DECL(void) VMR3NotifyGlobalFFU(PUVM pUVM, uint32_t fFlags); + VMMR3_INT_DECL(void) VMR3NotifyCpuFFU(PUVMCPU pUVMCpu, uint32_t fFlags); + VMMR3DECL(int) VMR3NotifyCpuDeviceReady(PVM pVM, VMCPUID idCpu); + VMMR3_INT_DECL(int) VMR3WaitHalted(PVM pVM, PVMCPU pVCpu, bool fIgnoreInterrupts); ++/*MYCODE*/ ++VMMDECL(uint8_t) VMR3GetFDPState(PUVM pUVM); ++VMMR3_INT_DECL(int) VMR3AddExecHardBreakpoint(PUVM pUVM, PVMCPU pVCpu, uint64_t GCPtr, uint8_t BreakpointId); ++VMMR3_INT_DECL(int) VMR3AddSoftBreakpoint(PUVM pUVM, PVMCPU pVCpu, uint8_t BreakpointAddressType, uint64_t BreakpointAddress, uint64_t BreakpointCr3); ++VMMR3_INT_DECL(int) VMR3AddPageBreakpoint(PUVM pUVM, PVMCPU pVCpu, uint8_t BreakpointId, uint8_t BreakpointAccessType, uint8_t BreakpointAddressType, uint64_t BreakpointAddress, uint64_t BreakpointLength); ++VMMR3_INT_DECL(int) VMR3AddMsrBreakpoint(PUVM pUVM, uint8_t BreakpointAccessType, uint64_t BreakpointAddress); ++VMMR3_INT_DECL(int) VMR3AddCrBreakpoint(PUVM pUVM, uint8_t BreakpointAccessType, uint64_t BreakpointAddress); ++VMMR3_INT_DECL(void) VMR3ClearBreakpoint(uint8_t BreakpointId); ++VMMR3_INT_DECL(bool) VMR3IsBreakpoint(uint64_t CurrentRIP); ++VMMDECL(int) VMR3PhysSimpleReadGCPhysU(PUVM pUVM, void *pvDst, RTGCPHYS GCPhysSrc, size_t cb); ++VMMDECL(int) VMR3PhysSimpleWriteGCPhysU(PUVM pUVM, const void *pvBuf, RTGCPHYS GCPhys, size_t cbWrite); ++VMMR3_INT_DECL(int) VMR3AddExecPageBreakpoint(PUVM pUVM, PVMCPU pVCpu, uint64_t GCPtr, uint64_t Length); ++VMMR3_INT_DECL(bool) VMR3RemoveBreakpoint(PUVM pUVM, int BreakpointId); ++VMMDECL(int) VMR3SingleStep(PUVM pUVM, PVMCPU pVCpu); ++VMMDECL(int) VMR3Break(PUVM pUVM); ++VMMDECL(int) VMR3Continue(PUVM pUVM); ++VMMDECL(bool) VMR3GetFDPRunning(PUVM pUVM); ++VMMDECL(void) VMR3SetFDPRunning(PUVM pUVM, bool newFDPRunning); ++VMMR3DECL(uint32_t) VMR3GetCPUCount(PUVM pUVM); ++VMMDECL(bool) VMR3HandleSingleStep(PVM pVM, PVMCPU pVCpu); ++VMMDECL(bool) VMR3EnterPause(PVM pVM, PVMCPU pVCpu); ++VMMDECL(void) VMR3SetFDPShm(PUVM pUVM, void *pFdpShm); ++VMMDECL(uint64_t) VMR3Test(PVMCPU pVCpu); ++VMMDECL(int) VMR3InjectInterrupt(PVM pVM, PVMCPU pVCpu, uint32_t enmXcpt, uint32_t uErr, uint64_t Cr2); ++VMMDECL(int) VMR3ClearInterrupt(PUVM pUVM, PVMCPU pVCpu); ++/*ENDMYCODE*/ + VMMR3_INT_DECL(int) VMR3WaitU(PUVMCPU pUVMCpu); + VMMR3DECL(int) VMR3WaitForDeviceReady(PVM pVM, VMCPUID idCpu); + VMMR3_INT_DECL(int) VMR3AsyncPdmNotificationWaitU(PUVMCPU pUVCpu); +diff --git a/include/VBox/vmm/vmm.h b/include/VBox/vmm/vmm.h +index 7fc1ccc6..869fcf68 100644 +--- a/include/VBox/vmm/vmm.h ++++ b/include/VBox/vmm/vmm.h +@@ -288,7 +288,11 @@ VMM_INT_DECL(void) VMMTrashVolatileXMMRegs(void); + VMM_INT_DECL(int) VMMPatchHypercall(PVM pVM, void *pvBuf, size_t cbBuf, size_t *pcbWritten); + VMM_INT_DECL(void) VMMHypercallsEnable(PVMCPU pVCpu); + VMM_INT_DECL(void) VMMHypercallsDisable(PVMCPU pVCpu); +- ++/*MYCODE*/ ++VMM_INT_DECL(bool) VMMMatchBreakpointId(PVM pVM, int BreakpointId, RTGCPHYS GCPhys, uint8_t BreakpointType, int BreakpointAccess); ++VMM_INT_DECL(int) VMMGetBreakpointId(PVM pVM, RTGCPHYS GCPhys, uint8_t BreakpointType, int BreakpointAccess); ++VMM_INT_DECL(int) VMMGetBreakpointIdFromPage(PVM pVM, RTGCPHYS GCPhys, uint8_t BreakpointType); ++/*ENDMYCODE*/ + + #if defined(IN_RING3) || defined(DOXYGEN_RUNNING) + /** @defgroup grp_vmm_api_r3 The VMM Host Context Ring 3 API +@@ -505,6 +509,8 @@ typedef enum VMMR0OPERATION + /** Test the 32->64 bits switcher. */ + VMMR0_DO_TEST_SWITCHER3264, + ++ VMMR0_DO_ALLOC_HCPHYS, ++ + /** The usual 32-bit type blow up. */ + VMMR0_DO_32BIT_HACK = 0x7fffffff + } VMMR0OPERATION; +diff --git a/src/VBox/Debugger/DBGCTcp.cpp b/src/VBox/Debugger/DBGCTcp.cpp +index 247d2e17..b28d6873 100644 +--- a/src/VBox/Debugger/DBGCTcp.cpp ++++ b/src/VBox/Debugger/DBGCTcp.cpp +@@ -30,6 +30,12 @@ + + #include + ++/*MYCODE*/ ++#include ++#include ++ ++#include ++/*MYCODE*/ + + /********************************************************************************************************************************* + * Structures and Typedefs * +@@ -205,6 +211,914 @@ static DECLCALLBACK(int) dbgcTcpConnection(RTSOCKET Sock, void *pvUser) + return rc; + } + ++/*MYCODE*/ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++ ++#define MIN(a,b) (((a)<(b))?(a):(b)) ++ ++#define DEBUG_LEVEL 0 ++#define DEBUG_FLOW 0 ++ ++#if DEBUG_LEVEL > 0 ++#define Log1(fmt,...) printf(fmt, ##__VA_ARGS__) ++#else ++#define Log1(fmt,...) ++#endif ++ ++#if DEBUG_LEVEL > 2 ++#define Log3(fmt,...) printf(fmt, ##__VA_ARGS__) ++#else ++#define Log3(fmt,...) ++#endif ++ ++#ifdef DEBUG_FLOW > 0 ++#define LogFloww() printf("%s\n", __FUNCTION__); ++#else ++#define LogFloww() ++#endif ++ ++typedef struct _MEMORY_SSM_T{ ++ uint8_t *pMemory; ++ uint64_t cbMemory; ++ uint64_t CurrentOffset; ++ uint64_t MaxOffset; ++}MEMORY_SSM_T; ++ ++typedef struct FDPVBOX_USERHANDLE_T{ ++ PUVM pUVM; ++ MEMORY_SSM_T* pMemorySSM; ++ FDP_SHM* pFDPServer; ++ uint64_t aVisibleGuestDebugRegisterSave[7]; ++ char TempBuffer[1*1024*1024]; ++}FDPVBOX_USERHANDLE_T; ++ ++bool FDPVBOX_Resume(void *pUserHandle) ++{ ++ LogFlow(("RESUME\n")); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ VMR3Continue(myVBOXHandle->pUVM); ++ return true; ++} ++ ++bool FDPVBOX_Pause(void *pUserHandle) ++{ ++ Log1("PAUSE !\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ VMR3Break(myVBOXHandle->pUVM); ++ return true; ++} ++ ++bool FDPVBOX_singleStep(void *pUserHandle, uint32_t CpuId) ++{ ++ LogFlow(("SINGLE_STEP\n")); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ int rc = VMR3SingleStep(myVBOXHandle->pUVM, pVCpu); ++ if(RT_SUCCESS(rc)){ ++ return true; ++ } ++ return false; ++} ++ ++bool FDPVBOX_getMemorySize(void *pUserHandle, uint64_t* MemorySize) ++{ ++ Log1("GET_PHYSICALMEMORYSIZE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ *MemorySize = MMR3PhysGetRamSizeU(myVBOXHandle->pUVM); ++ return true; ++} ++ ++bool FDPVBOX_readPhysicalMemory(void *pUserHandle, uint8_t *pDstBuffer, uint64_t PhysicalAddress, uint32_t ReadSize) ++{ ++ Log1("READ_PHYSICAL %p %d ... ", PhysicalAddress, ReadSize); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ int rc = VMR3PhysSimpleReadGCPhysU(myVBOXHandle->pUVM, pDstBuffer, PhysicalAddress, ReadSize); ++ Log1(" %s\n", RT_SUCCESS(rc) ? "OK" : "KO"); ++ if(RT_SUCCESS(rc)){ ++ return true; ++ } ++ return false; ++} ++ ++ ++bool FDPVBOX_writePhysicalMemory(void *pUserHandle, uint8_t *pSrcBuffer, uint64_t PhysicalAddress, uint32_t WriteSize) ++{ ++ Log1("WRITE_PHYSICAL %p %d...", PhysicalAddress, WriteSize); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ ++ //Check Read access ++ if(FDPVBOX_readPhysicalMemory(pUserHandle, (uint8_t*)myVBOXHandle->TempBuffer, PhysicalAddress, WriteSize) == false){ ++ return false; ++ } ++ //Effective Write ++ int rc = VMR3PhysSimpleWriteGCPhysU(myVBOXHandle->pUVM, pSrcBuffer, PhysicalAddress, WriteSize); ++ Log1(" %s\n", RT_SUCCESS(rc) ? "OK" : "KO"); ++ if(RT_SUCCESS(rc)){ ++ return true; ++ } ++ return false; ++} ++ ++ ++bool FDPVBOX_writeVirtualMemory(void *pUserHandle, uint32_t CpuId, uint8_t *pSrcBuffer, uint64_t VirtualAddress, uint32_t WriteSize) ++{ ++ Log1("writeVirtualMemory %p %d ...", VirtualAddress, WriteSize); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ int rc = PGMPhysSimpleWriteGCPtr(pVCpu, VirtualAddress, pSrcBuffer, WriteSize); ++ Log1(" %s\n", RT_SUCCESS(rc) ? "OK" : "KO"); ++ if(RT_SUCCESS(rc)){ ++ return true; ++ } ++ return false; ++} ++ ++bool FDPVBOX_writeMsr(void *pUserHandle, uint32_t CpuId, uint64_t MSRId, uint64_t MSRValue) ++{ ++ Log1("WRITE_MSR %p %p\n", MSRId, MSRValue); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ CPUMSetGuestMsr(pVCpu, MSRId, MSRValue); ++ //if(RT_SUCCESS(rc)){ ++ // return true; ++ //} ++ return false; ++} ++ ++bool FDPVBOX_getState(void *pUserHandle, uint8_t *currentState) ++{ ++ Log3("GET_STATE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ *currentState = VMR3GetFDPState(myVBOXHandle->pUVM); ++ return true; ++} ++ ++bool FDPVBOX_getCpuState(void *pUserHandle, uint32_t CpuId, uint8_t *pCurrentState) ++{ ++ Log1("GET_CPU_STATE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ *pCurrentState = pVCpu->mystate.s.u8StateBitmap; ++ return true; ++} ++ ++ ++bool FDPVBOX_getCpuCount(void *pUserHandle, uint32_t *pCpuCount) ++{ ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ *pCpuCount = VMR3GetCPUCount(myVBOXHandle->pUVM); ++ return true; ++} ++ ++bool FDPVBOX_readMsr(void *pUserHandle, uint32_t CpuId, uint64_t MsrId, uint64_t *pMsrValue) ++{ ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ CPUMQueryGuestMsr(pVCpu, MsrId, pMsrValue); ++ Log1("READ_MSR %p => %p\n", MsrId, *pMsrValue); ++ return true; ++} ++ ++bool FDPVBOX_readRegister(void *pUserHandle, uint32_t CpuId, FDP_Register RegisterId, uint64_t *pRegisterValue) ++{ ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ PCCPUMCTXCORE pCtxCore = CPUMGetGuestCtxCore(pVCpu); ++ PCPUMCTXCORE pRegFrame = (PCPUMCTXCORE)CPUMGetGuestCtxCore(pVCpu); ++ ++ switch(RegisterId){ ++ case FDP_CR0_REGISTER: *pRegisterValue = CPUMGetGuestCR0(pVCpu); break; ++ case FDP_CR2_REGISTER: *pRegisterValue = CPUMGetGuestCR2(pVCpu); break; ++ case FDP_CR3_REGISTER: *pRegisterValue = CPUMGetGuestCR3(pVCpu); break; ++ case FDP_CR4_REGISTER: *pRegisterValue = CPUMGetGuestCR4(pVCpu); break; ++ case FDP_CR8_REGISTER: *pRegisterValue = CPUMGetGuestCR8(pVCpu); break; ++ case FDP_RAX_REGISTER: *pRegisterValue = pCtxCore->rax; break; ++ case FDP_RBX_REGISTER: *pRegisterValue = pCtxCore->rbx; break; ++ case FDP_RCX_REGISTER: *pRegisterValue = pCtxCore->rcx; break; ++ case FDP_RDX_REGISTER: *pRegisterValue = pCtxCore->rdx; break; ++ case FDP_R8_REGISTER: *pRegisterValue = pCtxCore->r8; break; ++ case FDP_R9_REGISTER: *pRegisterValue = pCtxCore->r9; break; ++ case FDP_R10_REGISTER: *pRegisterValue = pCtxCore->r10; break; ++ case FDP_R11_REGISTER: *pRegisterValue = pCtxCore->r11; break; ++ case FDP_R12_REGISTER: *pRegisterValue = pCtxCore->r12; break; ++ case FDP_R13_REGISTER: *pRegisterValue = pCtxCore->r13; break; ++ case FDP_R14_REGISTER: *pRegisterValue = pCtxCore->r14; break; ++ case FDP_R15_REGISTER: *pRegisterValue = pCtxCore->r15; break; ++ case FDP_RSP_REGISTER: *pRegisterValue = pCtxCore->rsp; break; ++ case FDP_RBP_REGISTER: *pRegisterValue = pCtxCore->rbp; break; ++ case FDP_RSI_REGISTER: *pRegisterValue = pCtxCore->rsi; break; ++ case FDP_RDI_REGISTER: *pRegisterValue = pCtxCore->rdi; break; ++ case FDP_RIP_REGISTER: *pRegisterValue = pCtxCore->rip; break; ++ ++ //Visible for Guest Debug Register ++ case FDP_VDR0_REGISTER: *pRegisterValue = pVCpu->mystate.s.aGuestDr[0]; break; ++ case FDP_VDR1_REGISTER: *pRegisterValue = pVCpu->mystate.s.aGuestDr[1]; break; ++ case FDP_VDR2_REGISTER: *pRegisterValue = pVCpu->mystate.s.aGuestDr[2]; break; ++ case FDP_VDR3_REGISTER: *pRegisterValue = pVCpu->mystate.s.aGuestDr[3]; break; ++ case FDP_VDR6_REGISTER: *pRegisterValue = pVCpu->mystate.s.aGuestDr[6]; break; ++ case FDP_VDR7_REGISTER: *pRegisterValue = pVCpu->mystate.s.aGuestDr[7]; break; ++ ++ //Invisible for Guest Debug Register ++ case FDP_DR0_REGISTER: *pRegisterValue = CPUMGetGuestDR0(pVCpu); break; ++ case FDP_DR1_REGISTER: *pRegisterValue = CPUMGetGuestDR1(pVCpu); break; ++ case FDP_DR2_REGISTER: *pRegisterValue = CPUMGetGuestDR2(pVCpu); break; ++ case FDP_DR3_REGISTER: *pRegisterValue = CPUMGetGuestDR3(pVCpu); break; ++ case FDP_DR6_REGISTER: *pRegisterValue = CPUMGetGuestDR6(pVCpu); break; ++ case FDP_DR7_REGISTER: *pRegisterValue = CPUMGetGuestDR7(pVCpu); break; ++ ++ case FDP_CS_REGISTER: *pRegisterValue = CPUMGetGuestCS(pVCpu); break; ++ case FDP_DS_REGISTER: *pRegisterValue = CPUMGetGuestDS(pVCpu); break; ++ case FDP_ES_REGISTER: *pRegisterValue = CPUMGetGuestES(pVCpu); break; ++ case FDP_FS_REGISTER: *pRegisterValue = CPUMGetGuestFS(pVCpu); break; ++ case FDP_GS_REGISTER: *pRegisterValue = CPUMGetGuestGS(pVCpu); break; ++ case FDP_SS_REGISTER: *pRegisterValue = CPUMGetGuestSS(pVCpu); break; ++ case FDP_RFLAGS_REGISTER: *pRegisterValue = CPUMGetGuestEFlags(pVCpu); break; ++ case FDP_GDTRB_REGISTER: ++ { ++ VBOXGDTR gdtr = {0, 0}; ++ CPUMGetGuestGDTR(pVCpu,&gdtr); ++ *pRegisterValue = gdtr.pGdt; ++ break; ++ } ++ case FDP_GDTRL_REGISTER: ++ { ++ VBOXGDTR gdtr = {0, 0}; ++ CPUMGetGuestGDTR(pVCpu,&gdtr); ++ *pRegisterValue = gdtr.cbGdt; ++ break; ++ } ++ case FDP_IDTRB_REGISTER: ++ { ++ uint16_t cbIDT; ++ RTGCPTR GCPtrIDT = (RTGCPTR)CPUMGetGuestIDTR(pVCpu, &cbIDT); ++ *pRegisterValue = GCPtrIDT; ++ break; ++ } ++ case FDP_IDTRL_REGISTER: ++ { ++ uint16_t cbIDT; ++ RTGCPTR GCPtrIDT = (RTGCPTR)CPUMGetGuestIDTR(pVCpu, &cbIDT); ++ *pRegisterValue = cbIDT; ++ break; ++ } ++ case FDP_LDTR_REGISTER: ++ { ++ uint64_t Ldtrb; ++ uint32_t Ldtrl; ++ *pRegisterValue = CPUMGetGuestLdtrEx(pVCpu, &Ldtrb, &Ldtrl); ++ break; ++ } ++ case FDP_LDTRB_REGISTER: ++ { ++ uint64_t Ldtrb; ++ uint32_t Ldtrl; ++ CPUMGetGuestLdtrEx(pVCpu, &Ldtrb, &Ldtrl); ++ *pRegisterValue = Ldtrb; ++ break; ++ } ++ case FDP_LDTRL_REGISTER: ++ { ++ uint64_t Ldtrb; ++ uint32_t Ldtrl; ++ CPUMGetGuestLdtrEx(pVCpu, &Ldtrb, &Ldtrl); ++ *pRegisterValue = Ldtrl; ++ break; ++ } ++ case FDP_TR_REGISTER: ++ { ++ *pRegisterValue = CPUMGetGuestTR(pVCpu, NULL); ++ break; ++ } ++ default: ++ { ++ *pRegisterValue = 0xBADBADBADBADBADB; ++ return false; ++ } ++ } ++ return true; ++} ++ ++bool FDPVBOX_writeRegister(void *pUserHandle, uint32_t CpuId, FDP_Register RegisterId, uint64_t RegisterValue) ++{ ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ PCCPUMCTXCORE pCtxCore = CPUMGetGuestCtxCore(pVCpu); ++ PCPUMCTXCORE pRegFrame = (PCPUMCTXCORE)CPUMGetGuestCtxCore(pVCpu); ++ ++ FDP_CPU_CTX* pFdpCpuCtx = (FDP_CPU_CTX *)pVCpu->mystate.s.pCpuShm; ++ ++ switch(RegisterId){ ++ case FDP_RAX_REGISTER: pRegFrame->rax = RegisterValue; pFdpCpuCtx->rax = RegisterValue; break; ++ case FDP_RBX_REGISTER: pRegFrame->rbx = RegisterValue; pFdpCpuCtx->rbx = RegisterValue; break; ++ case FDP_RCX_REGISTER: pRegFrame->rcx = RegisterValue; pFdpCpuCtx->rcx = RegisterValue; break; ++ case FDP_RDX_REGISTER: pRegFrame->rdx = RegisterValue; pFdpCpuCtx->rdx = RegisterValue; break; ++ case FDP_R8_REGISTER: pRegFrame->r8 = RegisterValue; pFdpCpuCtx->r8 = RegisterValue; break; ++ case FDP_R9_REGISTER: pRegFrame->r9 = RegisterValue; pFdpCpuCtx->r9 = RegisterValue; break; ++ case FDP_R10_REGISTER: pRegFrame->r10 = RegisterValue; pFdpCpuCtx->r10 = RegisterValue; break; ++ case FDP_R11_REGISTER: pRegFrame->r11 = RegisterValue; pFdpCpuCtx->r11 = RegisterValue; break; ++ case FDP_R12_REGISTER: pRegFrame->r12 = RegisterValue; pFdpCpuCtx->r12 = RegisterValue; break; ++ case FDP_R13_REGISTER: pRegFrame->r13 = RegisterValue; pFdpCpuCtx->r13 = RegisterValue; break; ++ case FDP_R14_REGISTER: pRegFrame->r14 = RegisterValue; pFdpCpuCtx->r14 = RegisterValue; break; ++ case FDP_R15_REGISTER: pRegFrame->r15 = RegisterValue; pFdpCpuCtx->r15 = RegisterValue; break; ++ case FDP_RSP_REGISTER: pRegFrame->rsp = RegisterValue; pFdpCpuCtx->rsp = RegisterValue; break; ++ case FDP_RBP_REGISTER: pRegFrame->rbp = RegisterValue; pFdpCpuCtx->rbp = RegisterValue; break; ++ case FDP_RSI_REGISTER: pRegFrame->rsi = RegisterValue; pFdpCpuCtx->rsi = RegisterValue; break; ++ case FDP_RDI_REGISTER: pRegFrame->rdi = RegisterValue; pFdpCpuCtx->rdi = RegisterValue; break; ++ case FDP_RIP_REGISTER: pRegFrame->rip = RegisterValue; pFdpCpuCtx->rip = RegisterValue; break; ++ ++ //Invisible for Guest Debug Register ++ case FDP_DR0_REGISTER: CPUMSetGuestDR0(pVCpu, RegisterValue); break; ++ case FDP_DR1_REGISTER: CPUMSetGuestDR1(pVCpu, RegisterValue); break; ++ case FDP_DR2_REGISTER: CPUMSetGuestDR2(pVCpu, RegisterValue); break; ++ case FDP_DR3_REGISTER: CPUMSetGuestDR3(pVCpu, RegisterValue); break; ++ case FDP_DR6_REGISTER: CPUMSetGuestDR6(pVCpu, RegisterValue); break; ++ case FDP_DR7_REGISTER: CPUMSetGuestDR7(pVCpu, RegisterValue); break; ++ ++ //Visible for Guest Debug Register ++ case FDP_VDR0_REGISTER: pVCpu->mystate.s.aGuestDr[0] = RegisterValue; break; ++ case FDP_VDR1_REGISTER: pVCpu->mystate.s.aGuestDr[1] = RegisterValue; break; ++ case FDP_VDR2_REGISTER: pVCpu->mystate.s.aGuestDr[2] = RegisterValue; break; ++ case FDP_VDR3_REGISTER: pVCpu->mystate.s.aGuestDr[3] = RegisterValue; break; ++ case FDP_VDR6_REGISTER: pVCpu->mystate.s.aGuestDr[6] = RegisterValue; break; ++ case FDP_VDR7_REGISTER: pVCpu->mystate.s.aGuestDr[7] = RegisterValue; break; ++ ++ case FDP_CS_REGISTER: CPUMSetGuestCS(pVCpu, RegisterValue); break; ++ case FDP_DS_REGISTER: CPUMSetGuestDS(pVCpu, RegisterValue); break; ++ case FDP_ES_REGISTER: CPUMSetGuestES(pVCpu, RegisterValue); break; ++ case FDP_FS_REGISTER: CPUMSetGuestFS(pVCpu, RegisterValue); break; ++ case FDP_GS_REGISTER: CPUMSetGuestGS(pVCpu, RegisterValue); break; ++ case FDP_SS_REGISTER: CPUMSetGuestSS(pVCpu, RegisterValue); break; ++ case FDP_CR0_REGISTER: CPUMSetGuestCR0(pVCpu, RegisterValue); pFdpCpuCtx->cr0 = RegisterValue; break; ++ case FDP_CR2_REGISTER: CPUMSetGuestCR2(pVCpu, RegisterValue); pFdpCpuCtx->cr3 = RegisterValue; break; ++ case FDP_CR3_REGISTER: ++ { ++ CPUMSetGuestCR3(pVCpu, RegisterValue); ++ PGMFlushTLB(pVCpu, RegisterValue, 0); ++ pFdpCpuCtx->cr3 = RegisterValue; ++ break; ++ } ++ case FDP_CR4_REGISTER: CPUMSetGuestCR4(pVCpu, RegisterValue); pFdpCpuCtx->cr4 = RegisterValue; break; ++ //case FDP_CR8_REGISTER: CPUMSetGuestCR8(pVCpu, RegisterValue); break; ++ case FDP_RFLAGS_REGISTER: CPUMSetGuestEFlags(pVCpu, RegisterValue); break; ++ default: break; ++ } ++ return true; ++} ++ ++bool FDPVBOX_virtualToPhysical(void *pUserHandle, uint32_t CpuId, uint64_t VirtualAddress, uint64_t *PhysicalAddress) ++{ ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ //int rc = PGMPhysGCPtr2GCPhys(pVCpu, VirtualAddress, PhysicalAddress); ++ int rc = PGMGstGetPage(pVCpu, VirtualAddress, NULL, PhysicalAddress); ++ if(RT_FAILURE(rc)){ ++ return false; ++ } ++ return true; ++} ++ ++bool FDPVBOX_unsetBreakpoint(void *pUserHandle, uint8_t BreakpointId) ++{ ++ Log1("UNSET_BP [%d] ! \n", BreakpointId); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ int rc = VMR3RemoveBreakpoint(myVBOXHandle->pUVM, BreakpointId); ++ if(RT_SUCCESS(rc)){ ++ return true; ++ } ++ return false; ++} ++ ++bool FDPVBOX_getFxState64(void *pUserHandle, uint32_t CpuId, uint8_t *pDstBuffer, uint32_t *pDstSize) ++{ ++ Log1("GET_FXSTATE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ PCPUMCTX pCtx = CPUMQueryGuestCtxPtr(pVCpu); ++ PX86FXSTATE pFpuCtx = &pCtx->CTX_SUFF(pXState)->x87; ++ memcpy(pDstBuffer, pFpuCtx, sizeof(X86FXSTATE)); ++ *pDstSize = sizeof(X86FXSTATE); ++ return true; ++} ++ ++bool FDPVBOX_setFxState64(void *pUserHandle, uint32_t CpuId, uint8_t *pSrcBuffer, uint32_t uSrcSize) ++{ ++ Log1("SET_FXSTATE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ PCPUMCTX pCtx = CPUMQueryGuestCtxPtr(pVCpu); ++ PX86FXSTATE pFpuCtx = &pCtx->CTX_SUFF(pXState)->x87; ++ memcpy(pFpuCtx, pSrcBuffer, sizeof(X86FXSTATE)); ++ return true; ++} ++ ++bool FDPVBOX_readVirtualMemory(void *pUserHandle, uint32_t CpuId, uint64_t VirtualAddress, uint32_t ReadSize, uint8_t *pDstBuffer) ++{ ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ int rc = 0; ++ rc = PGMPhysSimpleReadGCPtr(pVCpu, pDstBuffer, VirtualAddress, ReadSize); ++ if(RT_SUCCESS(rc)){ ++ return true; ++ } ++ return false; ++} ++ ++int FDPVBOX_setBreakpoint( ++ void *pUserHandle, ++ uint32_t CpuId, ++ FDP_BreakpointType BreakpointType, ++ uint8_t BreakpointId, ++ FDP_Access BreakpointAccessType, ++ FDP_AddressType BreakpointAddressType, ++ uint64_t BreakpointAddress, ++ uint64_t BreakpointLength, ++ uint64_t BreakpointCr3) ++{ ++ Log1("SET_BREAKPOINT %p\n", BreakpointAddress); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return -1; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ BreakpointId = -1; ++ switch(BreakpointType){ ++ case FDP_SOFTHBP: ++ { ++ BreakpointId = VMR3AddSoftBreakpoint(myVBOXHandle->pUVM, pVCpu, BreakpointAddressType, BreakpointAddress, BreakpointCr3); ++ Log1("FDP_SOFTHBP[%d] %c %p %p\n", BreakpointId, BreakpointAddressType == 0x1 ? 'v' : 'p', BreakpointAddress, BreakpointCr3); ++ break; ++ } ++ case FDP_PAGEHBP: ++ { ++ BreakpointId = VMR3AddPageBreakpoint(myVBOXHandle->pUVM, pVCpu, -1, BreakpointAccessType, BreakpointAddressType, BreakpointAddress, BreakpointLength); ++ Log1("FDP_PAGEHBP[%d] %02x %p\n", BreakpointId, BreakpointAccessType, BreakpointAddress); ++ break; ++ } ++ case FDP_MSRHBP: ++ { ++ BreakpointId = VMR3AddMsrBreakpoint(myVBOXHandle->pUVM, BreakpointAccessType, BreakpointAddress); ++ Log1("FDP_MSRHBP[%d] %02x %p\n", BreakpointId, BreakpointAccessType, BreakpointAddress); ++ break; ++ } ++ case FDP_CRHBP: ++ { ++ BreakpointId = VMR3AddCrBreakpoint(myVBOXHandle->pUVM, BreakpointAccessType, BreakpointAddress); ++ Log1("FDP_CRHBP[%d] %02x %p\n", BreakpointId, BreakpointAccessType, BreakpointAddress); ++ break; ++ } ++ default: ++ { ++ Log1("Unknown BreakpointType!\n"); ++ break; ++ } ++ } ++ ++ return BreakpointId; ++} ++ ++ ++bool FDPVBOX_InjectInterrupt(void *pUserHandle, uint32_t CpuId, uint32_t InterruptionCode, uint32_t ErrorCode, uint64_t Cr2){ ++ Log1("InjectInterrupt\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ PUVM pUVM = myVBOXHandle->pUVM; ++ ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, 0); ++ ++ VMR3InjectInterrupt(NULL, pVCpu, InterruptionCode, ErrorCode, Cr2); ++ return true; ++} ++ ++bool FDPVBOX_Reboot(void *pUserHandle) ++{ ++ Log1("REBOOT\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ PUVM pUVM = myVBOXHandle->pUVM; ++ ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, 0); ++ ++ FDPVBOX_Pause(pUserHandle); ++ for(int BreakpointId = 0; BreakpointId < FDP_MAX_BREAKPOINT; BreakpointId++){ ++ FDPVBOX_unsetBreakpoint(pUserHandle, BreakpointId); ++ } ++ CPUMSetGuestDR7(pVCpu, 0); ++ ++ //Ask the EMT the Triple fault ++ pVCpu->mystate.s.bRebootRequired = true; ++ ++ FDPVBOX_Resume(pUserHandle); ++ ++ //TODO: Wait for the startup ++ usleep(100 * 1000); ++ ++ //Signal that the VM as changed, and what a change... ++ myVBOXHandle->pFDPServer->pSharedFDPSHM->stateChanged = true; ++ ++ return true; ++} ++ ++ ++ ++#include ++#include ++ ++ ++ ++static DECLCALLBACK(int) nullProgressCallback(PUVM pUVM, unsigned uPercent, void *pvUser) ++{ ++ NOREF(pUVM); ++ return VINF_SUCCESS; ++} ++ ++static DECLCALLBACK(int) pfnMemoryWrite(void *pvUser, uint64_t offStream, const void *pvBuf, size_t cbToWrite) ++{ ++ MEMORY_SSM_T* pMemorySSM = (MEMORY_SSM_T*)pvUser; ++ memcpy(pMemorySSM->pMemory+offStream, pvBuf, cbToWrite); ++ pMemorySSM->CurrentOffset = offStream; ++ if(offStream+cbToWrite > pMemorySSM->MaxOffset){ ++ pMemorySSM->MaxOffset = offStream+cbToWrite; ++ } ++ return VINF_SUCCESS; ++} ++ ++static DECLCALLBACK(int) pfnMemoryRead(void *pvUser, uint64_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead) ++{ ++ MEMORY_SSM_T* pMemorySSM = (MEMORY_SSM_T*)pvUser; ++ memcpy(pvBuf, pMemorySSM->pMemory+offStream, cbToRead); ++ *pcbRead = cbToRead; ++ pMemorySSM->CurrentOffset = offStream + cbToRead; ++ return VINF_SUCCESS; ++} ++ ++static DECLCALLBACK(int) pfnMemorySeek(void *pvUser, int64_t offSeek, unsigned uMethod, uint64_t *poffActual) ++{ ++ MEMORY_SSM_T* pMemorySSM = (MEMORY_SSM_T*)pvUser; ++ if(uMethod == RTFILE_SEEK_BEGIN){ ++ pMemorySSM->CurrentOffset = offSeek; ++ } ++ if(uMethod == RTFILE_SEEK_END){ ++ pMemorySSM->CurrentOffset = pMemorySSM->cbMemory-offSeek; ++ } ++ if(uMethod == RTFILE_SEEK_CURRENT){ ++ pMemorySSM->CurrentOffset += offSeek; ++ } ++ *poffActual = pMemorySSM->CurrentOffset; ++ if(*poffActual > pMemorySSM->MaxOffset){ ++ pMemorySSM->MaxOffset = *poffActual; ++ } ++ return VINF_SUCCESS; ++} ++ ++static DECLCALLBACK(uint64_t) pfnMemoryTell(void *pvUser) ++{ ++ MEMORY_SSM_T* pMemorySSM = (MEMORY_SSM_T*)pvUser; ++ return pMemorySSM->CurrentOffset; ++} ++ ++static DECLCALLBACK(int) pfnMemorySize(void *pvUser, uint64_t *pcb) ++{ ++ MEMORY_SSM_T* pMemorySSM = (MEMORY_SSM_T*)pvUser; ++ *pcb = pMemorySSM->MaxOffset; ++ return VINF_SUCCESS; ++} ++ ++static DECLCALLBACK(int) pfnMemoryIsOk(void *pvUser){ ++ return VINF_SUCCESS; ++} ++ ++static DECLCALLBACK(int) pfnMemoryClose(void *pvUser, bool fCancelled) ++{ ++ MEMORY_SSM_T* pMemorySSM = (MEMORY_SSM_T*)pvUser; ++ pMemorySSM->CurrentOffset = 0; ++ pMemorySSM->MaxOffset = 0; ++ return VINF_SUCCESS; ++} ++ ++static SSMSTRMOPS const g_ftmR3MemoryOps = ++{ ++ SSMSTRMOPS_VERSION, ++ pfnMemoryWrite, ++ pfnMemoryRead, ++ pfnMemorySeek, ++ pfnMemoryTell, ++ pfnMemorySize, ++ pfnMemoryIsOk, ++ pfnMemoryClose, ++ SSMSTRMOPS_VERSION ++}; ++ ++ ++ ++bool FDPVBOX_Save(void *pUserHandle) ++{ ++ Log1("SAVE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ PUVM pUVM = myVBOXHandle->pUVM; ++ ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, 0); ++ ++ //Avoid Interrupt during save, we don't want Interrupt in our save state ++ pVCpu->mystate.s.bDisableInterrupt = true; ++ ++ //Ask all CPU to suspend ++ printf("Save.FDPVBOX_Pause\n"); ++ FDPVBOX_Pause(pUserHandle); ++ ++ printf("Save.UsetBreakpoint\n"); ++ for(int BreakpointId = 0; BreakpointId < FDP_MAX_BREAKPOINT; BreakpointId++){ ++ FDPVBOX_unsetBreakpoint(pUserHandle, BreakpointId); ++ } ++ //Disable Hardware breakpoint ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR0_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR1_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR2_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR3_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR6_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR7_REGISTER, 0); ++ ++ FDPVBOX_readRegister(pUserHandle, 0, FDP_VDR0_REGISTER, &myVBOXHandle->aVisibleGuestDebugRegisterSave[0]); ++ FDPVBOX_readRegister(pUserHandle, 0, FDP_VDR1_REGISTER, &myVBOXHandle->aVisibleGuestDebugRegisterSave[1]); ++ FDPVBOX_readRegister(pUserHandle, 0, FDP_VDR2_REGISTER, &myVBOXHandle->aVisibleGuestDebugRegisterSave[2]); ++ FDPVBOX_readRegister(pUserHandle, 0, FDP_VDR3_REGISTER, &myVBOXHandle->aVisibleGuestDebugRegisterSave[3]); ++ FDPVBOX_readRegister(pUserHandle, 0, FDP_VDR6_REGISTER, &myVBOXHandle->aVisibleGuestDebugRegisterSave[6]); ++ FDPVBOX_readRegister(pUserHandle, 0, FDP_VDR7_REGISTER, &myVBOXHandle->aVisibleGuestDebugRegisterSave[7]); ++ ++ for(uint32_t i=0; imystate.s.bSuspendRequired = true; ++ } ++ ++ //Resume all CPU for suspend ++ printf("Save.FDPVBOX_Resume\n"); ++ FDPVBOX_Resume(pUserHandle); ++ ++ //Suspend all CPU ++ printf("Save.VMR3Suspend\n"); ++ VMR3Suspend(pUVM, VMSUSPENDREASON_USER); ++ for(uint32_t i=0; imystate.s.bSuspendRequired = false; ++ } ++ ++ //Alloc SaveState memory ++ if(myVBOXHandle->pMemorySSM->pMemory == NULL){ ++ myVBOXHandle->pMemorySSM->cbMemory = MMR3PhysGetRamSizeU(pUVM); ++ myVBOXHandle->pMemorySSM->pMemory = (uint8_t*)malloc(myVBOXHandle->pMemorySSM->cbMemory); ++ } ++ ++ //Set offset ++ myVBOXHandle->pMemorySSM->CurrentOffset = 0; ++ myVBOXHandle->pMemorySSM->MaxOffset = 0; ++ ++ //Save state ++ printf("Save.VMR3SaveFT\n"); ++ bool bSuspended = false; ++ VMR3SaveFT(pUVM, &g_ftmR3MemoryOps, (void*)myVBOXHandle->pMemorySSM, &bSuspended, true); ++ ++ for(uint32_t i=0; imystate.s.bRestoreRequired = true; ++ } ++ ++ printf("Save.VMR3Resume\n"); ++ VMR3Resume(pUVM, VMRESUMEREASON_STATE_RESTORED); ++ ++ printf("Save.FDPVBOX_Pause\n"); ++ FDPVBOX_Pause(pUserHandle); ++ ++ for(uint32_t i=0; imystate.s.bRestoreRequired = false; ++ } ++ ++ pVCpu->mystate.s.bDisableInterrupt = false; ++ ++ return true; ++} ++ ++bool FDPVBOX_Restore(void *pUserHandle) ++{ ++ Log1("RESTORE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ PUVM pUVM = myVBOXHandle->pUVM; ++ int rc; ++ if(myVBOXHandle->pMemorySSM->pMemory != NULL){ ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, 0); ++ ++ //Avoid Interrupt during save, we don't want Interrupt in our save state ++ pVCpu->mystate.s.bDisableInterrupt = true; ++ ++ printf("Restore.Pause\n"); ++ FDPVBOX_Pause(pUserHandle); ++ ++ printf("Restore.UsetBreakpoint\n"); ++ for(int BreakpointId = 0; BreakpointId < FDP_MAX_BREAKPOINT; BreakpointId++){ ++ FDPVBOX_unsetBreakpoint(pUserHandle, BreakpointId); ++ } ++ //Disable Hardware breakpoint ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR0_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR1_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR2_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR3_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR6_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR7_REGISTER, 0); ++ ++ printf("Restore.FDPVBOX_Resume\n"); ++ //Force console client to reconnect ++ FDPVBOX_Resume(pUserHandle); ++ ++ printf("Restore.VMR3Reset\n"); ++ VMR3Reset(pUVM); ++ ++ usleep(500 * 1000); ++ ++ printf("Restore.VMR3Suspend\n"); ++ rc = VMR3Suspend(pUVM, VMSUSPENDREASON_USER); ++ ++ ++ //rc = VMR3Suspend(pUVM, VMSUSPENDREASON_USER); ++ //printf("%d\n", rc); ++ ++ printf("Restore.VMR3LoadFromStream\n"); ++ VMR3LoadFromStream(pUVM, &g_ftmR3MemoryOps, (void*)myVBOXHandle->pMemorySSM, nullProgressCallback, NULL); ++ ++ printf("Restore.VMR3Resume\n"); ++ pVCpu->mystate.s.bRestoreRequired = true; ++ VMR3Resume(pUVM, VMRESUMEREASON_STATE_RESTORED); ++ ++ printf("Restore.FDPVBOX_Pause\n"); ++ FDPVBOX_Pause(pUserHandle); ++ pVCpu->mystate.s.bRestoreRequired = false; ++ ++ //Restore visible for Guest Debug Register ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_VDR0_REGISTER, myVBOXHandle->aVisibleGuestDebugRegisterSave[0]); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_VDR1_REGISTER, myVBOXHandle->aVisibleGuestDebugRegisterSave[1]); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_VDR2_REGISTER, myVBOXHandle->aVisibleGuestDebugRegisterSave[2]); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_VDR3_REGISTER, myVBOXHandle->aVisibleGuestDebugRegisterSave[3]); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_VDR6_REGISTER, myVBOXHandle->aVisibleGuestDebugRegisterSave[6]); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_VDR7_REGISTER, myVBOXHandle->aVisibleGuestDebugRegisterSave[7]); ++ ++ pVCpu->mystate.s.bDisableInterrupt = false; ++ ++ //printf("%d\n", VMR3ClearInterrupt(pUVM, NULL)); ++ ++ printf("Restore.Done!\n"); ++ return true; ++ } ++ ++ return false; ++} ++ ++void *CreateCPUSHM(PUVM pUVM) ++{ ++ int hMapFile; ++ void* pBuf; ++ ++ char aCpuShmName[512] = {0}; ++ strcpy(aCpuShmName,"CPU_"); ++ strcat(aCpuShmName, VMR3GetName(pUVM)); ++ ++ pBuf = CreateSHM(aCpuShmName, sizeof(FDP_CPU_CTX)); ++ if (pBuf == NULL) { ++ return NULL; ++ } ++ //Clear SHM ++ memset((void*)pBuf, 0, sizeof(FDP_CPU_CTX)); ++ ++ printf("0x%p\n", sizeof(FDP_CPU_CTX)); ++ ++ return pBuf; ++} ++ ++void* FDPServerThread(LPVOID lpParam) ++{ ++ PUVM pUVM = (PUVM)lpParam; ++ MEMORY_SSM_T MemorySSM; ++ MemorySSM.pMemory = NULL; ++ MemorySSM.CurrentOffset = 0; ++ ++ FDP_SHM* pFDPServer = FDP_CreateSHM((char*)VMR3GetName(pUVM)); ++ if(pFDPServer == NULL){ ++ printf("FDP SHM creation failed !\n"); ++ return NULL; ++ } ++ ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, 0); ++ //PCPUMCTX pCtx = CPUMQueryGuestCtxPtr(pVCpu); ++ void* pCpuShm = CreateCPUSHM(pUVM); ++ pVCpu->mystate.s.pCpuShm = pCpuShm; ++ if(pCpuShm == NULL){ ++ printf("Failed to CreateCpuShm\n"); ++ return NULL; ++ } ++ ++ printf("FDP_CreateSHM OK\n"); ++ FDPVBOX_USERHANDLE_T *pUserHandle = (FDPVBOX_USERHANDLE_T*)malloc(sizeof(FDPVBOX_USERHANDLE_T)); ++ pUserHandle->pUVM = pUVM; ++ pUserHandle->pMemorySSM = &MemorySSM; ++ pUserHandle->pFDPServer = pFDPServer; ++ ++ //Configure FDP Server Interface ++ FDP_SERVER_INTERFACE_T FDPServerInterface; ++ FDPServerInterface.pUserHandle = pUserHandle; ++ ++ FDPServerInterface.pfnGetState = &FDPVBOX_getState; ++ FDPServerInterface.pfnReadRegister = &FDPVBOX_readRegister; ++ FDPServerInterface.pfnWriteRegister = &FDPVBOX_writeRegister; ++ FDPServerInterface.pfnWritePhysicalMemory = &FDPVBOX_writePhysicalMemory; ++ FDPServerInterface.pfnWriteVirtualMemory = &FDPVBOX_writeVirtualMemory; ++ FDPServerInterface.pfnGetMemorySize = &FDPVBOX_getMemorySize; ++ FDPServerInterface.pfnResume = &FDPVBOX_Resume; ++ FDPServerInterface.pfnSingleStep = &FDPVBOX_singleStep; ++ FDPServerInterface.pfnPause = &FDPVBOX_Pause; ++ FDPServerInterface.pfnReadMsr = &FDPVBOX_readMsr; ++ FDPServerInterface.pfnWriteMsr = &FDPVBOX_writeMsr; ++ FDPServerInterface.pfnGetCpuCount = &FDPVBOX_getCpuCount; ++ FDPServerInterface.pfnGetCpuState = &FDPVBOX_getCpuState; ++ FDPServerInterface.pfnVirtualToPhysical = &FDPVBOX_virtualToPhysical; ++ FDPServerInterface.pfnUnsetBreakpoint = &FDPVBOX_unsetBreakpoint; ++ FDPServerInterface.pfnGetFxState64 = &FDPVBOX_getFxState64; ++ FDPServerInterface.pfnSetFxState64 = &FDPVBOX_setFxState64; ++ FDPServerInterface.pfnReadVirtualMemory = &FDPVBOX_readVirtualMemory; ++ FDPServerInterface.pfnReadPhysicalMemory = &FDPVBOX_readPhysicalMemory; ++ FDPServerInterface.pfnSetBreakpoint = &FDPVBOX_setBreakpoint; ++ FDPServerInterface.pfnReadPhysicalMemory = &FDPVBOX_readPhysicalMemory; ++ FDPServerInterface.pfnSave = &FDPVBOX_Save; ++ FDPServerInterface.pfnRestore = &FDPVBOX_Restore; ++ FDPServerInterface.pfnReboot = &FDPVBOX_Reboot; ++ FDPServerInterface.pfnInjectInterrupt = &FDPVBOX_InjectInterrupt; ++ ++ if (FDP_SetFDPServer(pFDPServer, &FDPServerInterface) == false){ ++ printf("Failed to FDP_SerFDPServer\n"); ++ return NULL; ++ } ++ ++ printf("FDP_SetFDPServer OK\n"); ++ ++ VMR3SetFDPShm(pUVM, pFDPServer); ++ ++ printf("VMR3SetFDPShm OK\n"); ++ ++ if (FDP_ServerLoop(pFDPServer) == false){ ++ printf("Failed to FDP_ServerLoop\n"); ++ return NULL; ++ } ++ ++ if(pUserHandle != NULL){ ++ free(pUserHandle); ++ } ++ ++ return NULL; ++} ++/*ENDMYCODE*/ ++ + + /** + * Spawns a new thread with a TCP based debugging console service. +@@ -215,6 +1129,10 @@ static DECLCALLBACK(int) dbgcTcpConnection(RTSOCKET Sock, void *pvUser) + */ + DBGDECL(int) DBGCTcpCreate(PUVM pUVM, void **ppvData) + { ++ /*MYCODE*/ ++ pthread_t t; ++ pthread_create(&t, NULL, FDPServerThread, pUVM); ++ /*ENDMYCODE*/ + /* + * Check what the configuration says. + */ +diff --git a/src/VBox/Devices/Audio/DevHDA.cpp b/src/VBox/Devices/Audio/DevHDA.cpp +index 0a90d6ff..a4ce56cb 100644 +--- a/src/VBox/Devices/Audio/DevHDA.cpp ++++ b/src/VBox/Devices/Audio/DevHDA.cpp +@@ -3501,12 +3501,12 @@ static int hdaR3SaveStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PHDASTREAM pStre + RTCircBufReleaseReadBlock(pStream->State.pCircBuf, 0 /* Don't advance read pointer -- see comment above */); + } + +- Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", ++ /*Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", + pStream->u8SD, + HDA_STREAM_REG(pThis, LPIB, pStream->u8SD), HDA_STREAM_REG(pThis, CBL, pStream->u8SD), HDA_STREAM_REG(pThis, LVI, pStream->u8SD))); +- ++*/ + #ifdef LOG_ENABLED +- hdaR3BDLEDumpAll(pThis, pStream->u64BDLBase, pStream->u16LVI + 1); ++ //hdaR3BDLEDumpAll(pThis, pStream->u64BDLBase, pStream->u16LVI + 1); + #endif + + return rc; +diff --git a/src/VBox/Devices/Audio/DevHDACommon.cpp b/src/VBox/Devices/Audio/DevHDACommon.cpp +index 43f4fb25..71d4eec4 100644 +--- a/src/VBox/Devices/Audio/DevHDACommon.cpp ++++ b/src/VBox/Devices/Audio/DevHDACommon.cpp +@@ -179,10 +179,10 @@ bool hdaR3WalClkSet(PHDASTATE pThis, uint64_t u64WalClk, bool fForce) + + const uint64_t u64WalClkNew = hdaWalClkGetCurrent(pThis); + +- Log3Func(("Cur: %RU64, New: %RU64 (force %RTbool) -> %RU64 %s\n", ++/* Log3Func(("Cur: %RU64, New: %RU64 (force %RTbool) -> %RU64 %s\n", + u64WalClkCur, u64WalClk, fForce, + u64WalClkNew, u64WalClkNew == u64WalClk ? "[OK]" : "[DELAYED]")); +- ++*/ + return (u64WalClkNew == u64WalClk); + } + +diff --git a/src/VBox/Devices/Network/DevE1000.cpp b/src/VBox/Devices/Network/DevE1000.cpp +index 21478b6b..3d256911 100644 +--- a/src/VBox/Devices/Network/DevE1000.cpp ++++ b/src/VBox/Devices/Network/DevE1000.cpp +@@ -3297,14 +3297,16 @@ static DECLCALLBACK(void) e1kTxDelayTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, v + PE1KSTATE pThis = (PE1KSTATE )pvUser; + Assert(PDMCritSectIsOwner(&pThis->csTx)); + +- E1K_INC_ISTAT_CNT(pThis->uStatTxDelayExp); ++ E1K_INC_ISTAT_CNT(pThis->uStatTxDelayExp) + # ifdef E1K_INT_STATS + uint64_t u64Elapsed = RTTimeNanoTS() - pThis->u64ArmedAt; + if (u64Elapsed > pThis->uStatMaxTxDelay) + pThis->uStatMaxTxDelay = u64Elapsed; + # endif + int rc = e1kXmitPending(pThis, false /*fOnWorkerThread*/); +- AssertMsg(RT_SUCCESS(rc) || rc == VERR_TRY_AGAIN, ("%Rrc\n", rc)); ++ /*MYCODE*/ ++ //AssertMsg(RT_SUCCESS(rc) || rc == VERR_TRY_AGAIN, ("%Rrc\n", rc)); ++ /*ENDMYCODE*/ + } + # endif /* E1K_TX_DELAY */ + +diff --git a/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp b/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp +index 4498678e..8024b794 100644 +--- a/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp ++++ b/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp +@@ -1029,10 +1029,10 @@ static int lsilogicR3ProcessMessageRequest(PLSILOGICSCSI pThis, PMptMessageHdr p + bool fForceReplyPostFifo = false; + + # ifdef LOG_ENABLED +- if (pMessageHdr->u8Function < RT_ELEMENTS(g_apszMPTFunctionNames)) ++ /*if (pMessageHdr->u8Function < RT_ELEMENTS(g_apszMPTFunctionNames)) + Log(("Message request function: %s\n", g_apszMPTFunctionNames[pMessageHdr->u8Function])); + else +- Log(("Message request function: \n")); ++ Log(("Message request function: \n"));*/ + # endif + + memset(pReply, 0, sizeof(MptReplyUnion)); +diff --git a/src/VBox/Frontends/VBoxSDL/Framebuffer.cpp b/src/VBox/Frontends/VBoxSDL/Framebuffer.cpp +index 0d681337..5ec8860c 100644 +--- a/src/VBox/Frontends/VBoxSDL/Framebuffer.cpp ++++ b/src/VBox/Frontends/VBoxSDL/Framebuffer.cpp +@@ -1104,7 +1104,9 @@ void VBoxSDLFB::repaint() + { + AssertMsg(gSdlNativeThread == RTThreadNativeSelf(), ("Wrong thread! SDL is not threadsafe!\n")); + LogFlow(("VBoxSDLFB::repaint\n")); +- update(0, 0, mScreen->w, mScreen->h, false /* fGuestRelative */); ++ if (mScreen != NULL) { ++ update(0, 0, mScreen->w, mScreen->h, false /* fGuestRelative */); ++ } + } + + /** +diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/DarwinKeyboard.cpp b/src/VBox/Frontends/VirtualBox/src/platform/darwin/DarwinKeyboard.cpp +index 29b4b0e6..3cc19107 100644 +--- a/src/VBox/Frontends/VirtualBox/src/platform/darwin/DarwinKeyboard.cpp ++++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/DarwinKeyboard.cpp +@@ -23,7 +23,7 @@ + *********************************************************************************************************************************/ + #define LOG_GROUP LOG_GROUP_GUI + +-#define VBOX_WITH_KBD_LEDS_SYNC ++//#define VBOX_WITH_KBD_LEDS_SYNC + //#define VBOX_WITHOUT_KBD_LEDS_SYNC_FILTERING + + #include "DarwinKeyboard.h" +diff --git a/src/VBox/VMM/VMMAll/HMAll.cpp b/src/VBox/VMM/VMMAll/HMAll.cpp +index a46e6b64..e540eeaa 100644 +--- a/src/VBox/VMM/VMMAll/HMAll.cpp ++++ b/src/VBox/VMM/VMMAll/HMAll.cpp +@@ -197,6 +197,21 @@ static void hmPokeCpuForTlbFlush(PVMCPU pVCpu, bool fAccountFlushStat) + STAM_COUNTER_INC(&pVCpu->hm.s.StatFlushPageManual); + } + ++/*MYCODE*/ ++//Ensure that all CPU are paused ! ++VMM_INT_DECL(int) HMFlushTLBOnAllVCpus2(PVM pVM) ++{ ++ for (VMCPUID idCpu = 0; idCpu < pVM->cCpus; idCpu++){ ++ PVMCPU pVCpu = &pVM->aCpus[idCpu]; ++ HMFlushTLB(pVCpu); ++ VMCPU_FF_SET(pVCpu, VMCPU_FF_TLB_FLUSH); ++ hmPokeCpuForTlbFlush(pVCpu, true /* fAccountFlushStat */); ++ } ++ ++ return VINF_SUCCESS; ++} ++/*ENDMYCODE*/ ++ + + /** + * Invalidates a guest page on all VCPUs. +diff --git a/src/VBox/VMM/VMMAll/PGMAll.cpp b/src/VBox/VMM/VMMAll/PGMAll.cpp +index 1202236a..f5bc7a63 100644 +--- a/src/VBox/VMM/VMMAll/PGMAll.cpp ++++ b/src/VBox/VMM/VMMAll/PGMAll.cpp +@@ -1335,6 +1335,272 @@ static int pgmShwGetEPTPDPtr(PVMCPU pVCpu, RTGCPTR64 GCPtr, PEPTPDPT *ppPdpt, PE + return VINF_SUCCESS; + } + ++/*MYCODE*/ ++VMMDECL(int) PGMShwGetHCPage(PVMCPU pVCpu, uint64_t GCPhys, uint64_t *HCPhys) ++{ ++ PEPTPDPT ppPdpt; ++ PEPTPD pShwPD; ++ int rc; ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ pgmLock(pVM); ++ ++ const unsigned iPd = ((GCPhys >> SHW_PD_SHIFT) & SHW_PD_MASK); ++ rc = pgmShwGetEPTPDPtr(pVCpu, GCPhys, &ppPdpt, &pShwPD); ++ EPTPDE *Pde; ++ Pde = &pShwPD->a[iPd]; ++ ++ if(Pde->n.u1Size == 1) ++ { //2M ++ uint64_t test = *((uint64_t*)&Pde->b); ++ *HCPhys = (test & 0xFFFFFFFFFFE00000); ++ }else ++ { //4K ++ PSHWPT pPT; ++ rc = PGM_HCPHYS_2_PTR(pVM, pVCpu, Pde->u & SHW_PDE_PG_MASK, &pPT); ++ if (RT_SUCCESS(rc)) ++ { ++ const unsigned iPt = (GCPhys >> SHW_PT_SHIFT) & SHW_PT_MASK; ++ EPTPTE *Pte = &pPT->a[iPt]; ++ ++ uint64_t test = *((uint64_t*)&Pte->n); ++ *HCPhys = (test & 0xFFFFFFFFFFFFF000); ++ } ++ } ++ pgmUnlock(pVM); ++ return rc; ++} ++ ++void logRelPDE(EPTPDE *Pde) ++{ ++ LogRel(("Pde->b.u1Present %p\n", Pde->b.u1Present)); ++ LogRel(("Pde->b.u1Write %p\n", Pde->b.u1Write)); ++ LogRel(("Pde->b.u1Execute %p\n", Pde->b.u1Execute)); ++ LogRel(("Pde->b.u3EMT %p\n", Pde->b.u3EMT)); ++ LogRel(("Pde->b.u1IgnorePAT %p\n", Pde->b.u1IgnorePAT)); ++ LogRel(("Pde->b.u1Size %p\n", Pde->b.u1Size)); ++ LogRel(("Pde->b.u4Available %p\n", Pde->b.u4Available)); ++ LogRel(("Pde->b.u9Reserved %p\n", Pde->b.u9Reserved)); ++ LogRel(("Pde->b.u31PhysAddr %p\n", Pde->b.u31PhysAddr)); ++ LogRel(("Pde->b.u12Available %p\n", Pde->b.u12Available)); ++} ++ ++void logRelPTE(EPTPTE *Pte) ++{ ++ LogRel(("------------------------------------\n")); ++ LogRel(("Pte->n.u1Present %p\n", Pte->n.u1Present)); ++ LogRel(("Pte->n.u1Write %p\n", Pte->n.u1Write)); ++ LogRel(("Pte->n.u1Execute %p\n", Pte->n.u1Execute)); ++ LogRel(("------------------------------------\n")); ++} ++ ++VMMDECL(int) PGMShwSetHCPage(PVMCPU pVCpu, uint64_t GCPhys, uint64_t HCPhys) ++{ ++ PEPTPDPT ppPdpt; ++ PEPTPD pShwPD; ++ int rc; ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ pgmLock(pVM); ++ ++ const unsigned iPd = ((GCPhys >> SHW_PD_SHIFT) & SHW_PD_MASK); ++ rc = pgmShwGetEPTPDPtr(pVCpu, GCPhys, &ppPdpt, &pShwPD); ++ EPTPDE *Pde; ++ Pde = &pShwPD->a[iPd]; ++ ++ if(Pde->n.u1Size == 1){ //2M ++ Pde->au64[0] = (Pde->au64[0] & 0x1FFFFF) | (HCPhys & 0xFFFFFFFFFFE00000); ++ }else{ //4K ++ PSHWPT pPT; ++ rc = PGM_HCPHYS_2_PTR(pVM, pVCpu, Pde->u & SHW_PDE_PG_MASK, &pPT); ++ if (RT_SUCCESS(rc)){ ++ const unsigned iPt = (GCPhys >> SHW_PT_SHIFT) & SHW_PT_MASK; ++ EPTPTE *Pte = &pPT->a[iPt]; ++ ++ Pte->au64[0] = (Pte->au64[0] & 0xFFF) | (HCPhys & 0xFFFFFFFFFFFFF000); ++ } ++ } ++ ++ pgmUnlock(pVM); ++ return rc; ++} ++ ++VMMDECL(int) PGMShwChangeFlags(PVMCPU pVCpu, uint64_t GCPhys, uint8_t orPresent, uint8_t andPresent, uint8_t orWrite, uint8_t andWrite, uint8_t orExecute, uint8_t andExecute) ++{ ++ PEPTPDPT ppPdpt; ++ PEPTPD pShwPD; ++ int rc; ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ pgmLock(pVM); ++ ++ const unsigned iPd = ((GCPhys >> SHW_PD_SHIFT) & SHW_PD_MASK); ++ rc = pgmShwGetEPTPDPtr(pVCpu, GCPhys, &ppPdpt, &pShwPD); ++ EPTPDE *Pde; ++ Pde = &pShwPD->a[iPd]; ++ ++ if(Pde->n.u1Size == 1){ //2M ++ Pde->b.u1Present = (Pde->b.u1Present | orPresent) & andPresent; ++ Pde->b.u1Write = (Pde->b.u1Write | orWrite) & andWrite; ++ Pde->b.u1Execute = (Pde->b.u1Execute | orExecute) & andExecute; ++ }else{ //4K ++ PSHWPT pPT; ++ rc = PGM_HCPHYS_2_PTR(pVM, pVCpu, Pde->u & SHW_PDE_PG_MASK, &pPT); ++ if (RT_SUCCESS(rc)){ ++ const unsigned iPt = (GCPhys >> SHW_PT_SHIFT) & SHW_PT_MASK; ++ EPTPTE *Pte = &pPT->a[iPt]; ++ ++ Pte->n.u1Present = (Pte->n.u1Present | orPresent) & andPresent; ++ Pte->n.u1Write = (Pte->n.u1Write | orWrite) & andWrite; ++ Pte->n.u1Execute = (Pte->n.u1Execute | orExecute) & andExecute; ++ } ++ } ++ ++ pgmUnlock(pVM); ++ return rc; ++} ++ ++#define MEMORY_SIZE 0x80000000 ++ ++VMMDECL(int) PGMShwSaveRights(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ PEPTPDPT ppPdpt; ++ PEPTPD pShwPD; ++ int returnFlags = 0; ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ pgmLock(pVM); ++ ++ const unsigned iPd = ((GCPhys >> SHW_PD_SHIFT) & SHW_PD_MASK); ++ int rc = pgmShwGetEPTPDPtr(pVCpu, GCPhys, &ppPdpt, &pShwPD); ++ EPTPDE *Pde; ++ Pde = &pShwPD->a[iPd]; ++ ++ if(Pde->n.u1Size == 1){ //2M ++ //TODO !!!! ++ }else{ //4K ++ PSHWPT pPT; ++ rc = PGM_HCPHYS_2_PTR(pVM, pVCpu, Pde->u & SHW_PDE_PG_MASK, &pPT); ++ if (RT_SUCCESS(rc)){ ++ const unsigned iPt = (GCPhys >> SHW_PT_SHIFT) & SHW_PT_MASK; ++ EPTPTE *pPte = &pPT->a[iPt]; ++ ++ uint32_t PfnIndex = ((GCPhys & X86_PAGE_4K_BASE_MASK) >> X86_PAGE_4K_SHIFT); ++ ++ PfnEntrie_t* pTmpPfnEntrie; ++#ifdef IN_RING0 ++ pTmpPfnEntrie = pVM->mystate.s.pPfnTableR0; ++#else ++ pTmpPfnEntrie = pVM->mystate.s.pPfnTableR3; ++#endif ++ pTmpPfnEntrie[PfnIndex].u.u1Present = pPte->n.u1Present; ++ pTmpPfnEntrie[PfnIndex].u.u1Write = pPte->n.u1Write; ++ pTmpPfnEntrie[PfnIndex].u.u1Execute = pPte->n.u1Execute; ++ ++ //logRelPTE(Pte); ++ } ++ } ++ ++ pgmUnlock(pVM); ++ return rc; ++} ++ ++VMMDECL(int) PGMShwRestoreRights(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ if(GCPhys < MEMORY_SIZE) ++ { ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ uint32_t PfnIndex = ((GCPhys & X86_PAGE_4K_BASE_MASK) >> X86_PAGE_4K_SHIFT); ++ ++ ++ PfnEntrie_t* tmpPfnEntrie; ++#ifdef IN_RING0 ++ tmpPfnEntrie = pVM->mystate.s.pPfnTableR0; ++#else ++ tmpPfnEntrie = pVM->mystate.s.pPfnTableR3; ++#endif ++ PGMShwChangeFlags(pVCpu, GCPhys, ++ tmpPfnEntrie[PfnIndex].u.u1Present,tmpPfnEntrie[PfnIndex].u.u1Present, ++ tmpPfnEntrie[PfnIndex].u.u1Write, tmpPfnEntrie[PfnIndex].u.u1Write, ++ tmpPfnEntrie[PfnIndex].u.u1Execute, tmpPfnEntrie[PfnIndex].u.u1Execute); ++ } ++ ++ return VINF_SUCCESS; ++} ++ ++VMMDECL(int) PGMShwSetBreakable(PVMCPU pVCpu, uint64_t GCPhys, bool Breakable) ++{ ++ if(GCPhys < MEMORY_SIZE){ ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ uint32_t PfnIndex = ((GCPhys & X86_PAGE_4K_BASE_MASK) >> X86_PAGE_4K_SHIFT); ++ ++ PfnEntrie_t* tmpPfnEntrie; ++#ifdef IN_RING0 ++ tmpPfnEntrie = pVM->mystate.s.pPfnTableR0; ++#else ++ tmpPfnEntrie = pVM->mystate.s.pPfnTableR3; ++#endif ++ tmpPfnEntrie[PfnIndex].u.u1Breakable = Breakable; ++ //LogRel(("PGMShwSetBreakable %p PfnTable[%d].u1Breakable %s\n", GCPhys, PfnIndex, Breakable ? "true" : "false")); ++ } ++ return VINF_SUCCESS; ++} ++ ++ ++VMMDECL(bool) PGMShwIsBreakable(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ if(GCPhys < MEMORY_SIZE){ ++ uint32_t PfnIndex = ((GCPhys & X86_PAGE_4K_BASE_MASK) >> X86_PAGE_4K_SHIFT); ++ PfnEntrie_t* tmpPfnEntrie; ++#ifdef IN_RING0 ++ tmpPfnEntrie = pVM->mystate.s.pPfnTableR0; ++#else ++ tmpPfnEntrie = pVM->mystate.s.pPfnTableR3; ++#endif ++ //LogRel(("PGMShwIsBreakable %p PfnTable[%d].u1Breakable %s\n", GCPhys, PfnIndex, tmpPfnEntrie[PfnIndex].u.u1Breakable ? "true" : "false")); ++ return tmpPfnEntrie[PfnIndex].u.u1Breakable; ++ } ++ return false; ++} ++ ++VMMDECL(int) PGMShwNoPresent(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ return PGMShwChangeFlags(pVCpu, GCPhys, 0, 0, 0, 1, 0, 1); ++} ++ ++VMMDECL(int) PGMShwPresent(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ return PGMShwChangeFlags(pVCpu, GCPhys, 1, 1, 0, 1, 0, 1); ++} ++ ++VMMDECL(int) PGMShwNoWrite(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ return PGMShwChangeFlags(pVCpu, GCPhys, 0, 1, 0, 0, 0, 1); ++} ++ ++VMMDECL(int) PGMShwWrite(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ return PGMShwChangeFlags(pVCpu, GCPhys, 0, 1, 1, 1, 0, 1); ++} ++ ++VMMDECL(int) PGMShwNoExecute(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ return PGMShwChangeFlags(pVCpu, GCPhys, 0, 1, 0, 1, 0, 0); ++} ++ ++VMMDECL(int) PGMShwExecute(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ return PGMShwChangeFlags(pVCpu, GCPhys, 0, 1, 0, 1, 1, 1); ++} ++ ++VMMDECL(int) PGMShwInvalidate(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++#ifndef IN_RING0 ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ HMFlushTLBOnAllVCpus2(pVM); ++#endif ++ return VINF_SUCCESS; ++} ++/*ENDMYCODE*/ ++ ++ + #endif /* IN_RC */ + + #ifdef IN_RING0 +diff --git a/src/VBox/VMM/VMMAll/VMMAll.cpp b/src/VBox/VMM/VMMAll/VMMAll.cpp +index d6d12df1..c1d427a9 100644 +--- a/src/VBox/VMM/VMMAll/VMMAll.cpp ++++ b/src/VBox/VMM/VMMAll/VMMAll.cpp +@@ -464,7 +464,6 @@ VMM_INT_DECL(void) VMMHypercallsEnable(PVMCPU pVCpu) + #endif + } + +- + /** + * Notifies VMM that paravirtualized hypercalls are now disabled. + * +@@ -481,3 +480,48 @@ VMM_INT_DECL(void) VMMHypercallsDisable(PVMCPU pVCpu) + #endif + } + ++/*MYCODE*/ ++VMM_INT_DECL(bool) VMMMatchBreakpointId(PVM pVM, int BreakpointId, RTGCPHYS GCPhys, uint8_t BreakpointType, int BreakpointAccess) ++{ ++ if(BreakpointId >= 0 ++ || BreakpointId < MAX_BREAKPOINT_ID){ ++ BreakpointEntrie_t *TempBreakpointEntrie = &pVM->bp.l[BreakpointId]; ++ if(TempBreakpointEntrie->breakpointActivated ++ && TempBreakpointEntrie->breakpointType == BreakpointType ++ && (TempBreakpointEntrie->breakpointAccessType & BreakpointAccess)){ ++ for(int j=0; jbreakpointGCPhysAreaCount; j++){ ++ if(GCPhys >= TempBreakpointEntrie->breakpointGCPhysAreaTable[j].Start ++ && GCPhys < TempBreakpointEntrie->breakpointGCPhysAreaTable[j].End){ ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++VMM_INT_DECL(int) VMMGetBreakpointId(PVM pVM, RTGCPHYS GCPhys, uint8_t BreakpointType, int BreakpointAccess) ++{ ++ for(int i=0; ibp.l[i].breakpointActivated ++ && pVM->bp.l[i].breakpointType == BreakpointType){ ++ for(int j=0; jbp.l[i].breakpointGCPhysAreaCount; j++){ ++ if((GCPhys & ~(_4K-1)) == (pVM->bp.l[i].breakpointGCPhysAreaTable[j].Start & ~(_4K-1))){ ++ return i; ++ } ++ } ++ } ++ } ++ return -1; ++} ++/*ENDMYCODE*/ +\ No newline at end of file +diff --git a/src/VBox/VMM/VMMR0/GVMMR0.cpp b/src/VBox/VMM/VMMR0/GVMMR0.cpp +index 2ae99e3b..3de10d74 100644 +--- a/src/VBox/VMM/VMMR0/GVMMR0.cpp ++++ b/src/VBox/VMM/VMMR0/GVMMR0.cpp +@@ -921,6 +921,24 @@ GVMMR0DECL(int) GVMMR0CreateVM(PSUPDRVSESSION pSession, uint32_t cCpus, PVM *ppV + AssertCompileMemberAlignment(VM, tm, 64); + AssertCompileMemberAlignment(VM, aCpus, PAGE_SIZE); + ++ /*MYCODE*/ ++ RTR0MEMOBJ PfnTableMemObj; ++ rc = RTR0MemObjAllocPage(&PfnTableMemObj, sizeof(PfnEntrie_t)*512*1024, false /* fExecutable */); ++ if (RT_SUCCESS(rc)){ ++ pVM->mystate.s.pPfnTableR0 = (PfnEntrie_t*)RTR0MemObjAddress(PfnTableMemObj); ++ RTR0MEMOBJ PfnTableMapObj; ++ rc = RTR0MemObjMapUser(&PfnTableMapObj, PfnTableMemObj, (RTR3PTR)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE, NIL_RTR0PROCESS); ++ if (RT_SUCCESS(rc)) { ++ pVM->mystate.s.pPfnTableR3 = (PfnEntrie_t*)RTR0MemObjAddressR3(PfnTableMapObj); ++ }else{ ++ return -1; ++ } ++ }else{ ++ return -1; ++ } ++ /*ENDMYCODE*/ ++ ++ + rc = RTR0MemObjAllocPage(&pGVM->gvmm.s.VMPagesMemObj, cPages * sizeof(SUPPAGE), false /* fExecutable */); + if (RT_SUCCESS(rc)) + { +diff --git a/src/VBox/VMM/VMMR0/HMVMXR0.cpp b/src/VBox/VMM/VMMR0/HMVMXR0.cpp +index efcfcbe9..f605c4fd 100644 +--- a/src/VBox/VMM/VMMR0/HMVMXR0.cpp ++++ b/src/VBox/VMM/VMMR0/HMVMXR0.cpp +@@ -40,6 +40,11 @@ + #include "HMVMXR0.h" + #include "dtrace/VBoxVMM.h" + ++/*MYCODE*/ ++#include "FDP/include/FDP.h" ++#include ++/*ENDMYCODE*/ ++ + #define HMVMX_USE_IEM_EVENT_REFLECTION + #ifdef DEBUG_ramshankar + # define HMVMX_ALWAYS_SAVE_GUEST_RFLAGS +@@ -53,6 +58,11 @@ + #endif + + ++/*MYCODE*/ ++#define HMVMX_ALWAYS_TRAP_ALL_XCPTS ++/*ENDMYCODE*/ ++ ++ + /********************************************************************************************************************************* + * Defined Constants And Macros * + *********************************************************************************************************************************/ +@@ -932,6 +942,11 @@ static void hmR0VmxStructsFree(PVM pVM) + */ + static int hmR0VmxStructsAlloc(PVM pVM) + { ++ /*MYCODE*/ ++ pVM->mystate.s.PageSpinlock = NIL_RTSPINLOCK; ++ RTSpinlockCreate(&pVM->mystate.s.PageSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, pVM->mystate.s.PageSpinLockName); ++ /*ENDMYCODE*/ ++ + /* + * Initialize members up-front so we can cleanup properly on allocation failure. + */ +@@ -4130,9 +4145,11 @@ static int hmR0VmxLoadSharedDebugState(PVMCPU pVCpu, PCPUMCTX pMixedCtx) + if (pVCpu->hm.s.vmx.u32EntryCtls & VMX_VMCS_CTRL_ENTRY_LOAD_DEBUG) + { + /* Validate. Intel spec. 17.2 "Debug Registers", recompiler paranoia checks. */ +- Assert((pMixedCtx->dr[7] & (X86_DR7_MBZ_MASK | X86_DR7_RAZ_MASK)) == 0); /* Bits 63:32, 15, 14, 12, 11 are reserved. */ +- Assert((pMixedCtx->dr[7] & X86_DR7_RA1_MASK) == X86_DR7_RA1_MASK); /* Bit 10 is reserved (RA1). */ +- } ++ /*MYCODE*/ ++ //Assert((pMixedCtx->dr[7] & (X86_DR7_MBZ_MASK | X86_DR7_RAZ_MASK)) == 0); /* Bits 63:32, 15, 14, 12, 11 are reserved. */ ++ //Assert((pMixedCtx->dr[7] & X86_DR7_RA1_MASK) == X86_DR7_RA1_MASK); /* Bit 10 is reserved (RA1). */ ++ /*ENDMYCODE*/ ++ } + #endif + + int rc; +@@ -4236,6 +4253,11 @@ static int hmR0VmxLoadSharedDebugState(PVMCPU pVCpu, PCPUMCTX pMixedCtx) + fInterceptMovDRx = true; + } + ++ /*MYCODE*/ ++ //Always intercept MovDRx ++ fInterceptMovDRx = true; ++ /*ENDMYCODE*/ ++ + /* Update guest DR7. */ + rc = VMXWriteVmcs32(VMX_VMCS_GUEST_DR7, pMixedCtx->dr[7]); + AssertRCReturn(rc, rc); +@@ -4243,6 +4265,12 @@ static int hmR0VmxLoadSharedDebugState(PVMCPU pVCpu, PCPUMCTX pMixedCtx) + pVCpu->hm.s.fUsingHyperDR7 = false; + } + ++ /*MYCODE*/ ++ //Always intercept DB ++ pVCpu->hm.s.vmx.u32XcptBitmap |= RT_BIT(X86_XCPT_DB); ++ HMCPU_CF_SET(pVCpu, HM_CHANGED_GUEST_XCPT_INTERCEPTS); ++ /*ENDMYCODE*/ ++ + /* + * Update the processor-based VM-execution controls regarding intercepting MOV DRx instructions. + */ +@@ -4250,6 +4278,16 @@ static int hmR0VmxLoadSharedDebugState(PVMCPU pVCpu, PCPUMCTX pMixedCtx) + pVCpu->hm.s.vmx.u32ProcCtls |= VMX_VMCS_CTRL_PROC_EXEC_MOV_DR_EXIT; + else + pVCpu->hm.s.vmx.u32ProcCtls &= ~VMX_VMCS_CTRL_PROC_EXEC_MOV_DR_EXIT; ++ ++ /*MYCODE*/ ++ //Always Intercept MovDrx ++ pVCpu->hm.s.vmx.u32ProcCtls |= VMX_VMCS_CTRL_PROC_EXEC_MOV_DR_EXIT; ++ pVCpu->hm.s.vmx.u32ProcCtls |= VMX_VMCS_CTRL_PROC_EXEC_CR3_LOAD_EXIT; ++ pVCpu->hm.s.vmx.u32ProcCtls |= VMX_VMCS_CTRL_PROC_EXEC_CR3_STORE_EXIT; ++ pVCpu->hm.s.vmx.u32ProcCtls |= VMX_VMCS_CTRL_PROC_EXEC_CR8_LOAD_EXIT; ++ pVCpu->hm.s.vmx.u32ProcCtls |= VMX_VMCS_CTRL_PROC_EXEC_CR8_STORE_EXIT; ++ /*ENDCODE*/ ++ + rc = VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.u32ProcCtls); + AssertRCReturn(rc, rc); + +@@ -10462,6 +10500,11 @@ static VBOXSTRICTRC hmR0VmxRunGuestCodeDebug(PVM pVM, PVMCPU pVCpu, PCPUMCTX pCt + STAM_PROFILE_ADV_STOP(&pVCpu->hm.s.StatExit2, x); + if (rcStrict != VINF_SUCCESS) + break; ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired){ ++ break; ++ } ++ /*ENDMYCODE*/ + if (cLoops > pVM->hm.s.cMaxResumeLoops) + { + STAM_COUNTER_INC(&pVCpu->hm.s.StatSwitchMaxResumeLoops); +@@ -12332,6 +12375,19 @@ HMVMX_EXIT_DECL hmR0VmxExitRdmsr(PVMCPU pVCpu, PCPUMCTX pMixedCtx, PVMXTRANSIENT + } + #endif + ++ /*MYCODE*/ ++ for(int iBreakpointId=0; iBreakpointIdCTX_SUFF(pVM)->bp.l[iBreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_MSRHBP ++ && pTempBreakpointEntrie->breakpointAccessType == FDP_READ_BP ++ && (pTempBreakpointEntrie->breakpointGCPtr == pMixedCtx->ecx || pTempBreakpointEntrie->breakpointGCPtr == 0)){ ++ pVCpu->mystate.s.bMsrHyperBreakPointHitted = true; ++ return VINF_EM_HALT; ++ } ++ } ++ /*ENDMYCODE*/ ++ + PVM pVM = pVCpu->CTX_SUFF(pVM); + rc = EMInterpretRdmsr(pVM, pVCpu, CPUMCTX2CORE(pMixedCtx)); + AssertMsg(rc == VINF_SUCCESS || rc == VERR_EM_INTERPRETER, +@@ -12367,6 +12423,20 @@ HMVMX_EXIT_DECL hmR0VmxExitWrmsr(PVMCPU pVCpu, PCPUMCTX pMixedCtx, PVMXTRANSIENT + AssertRCReturn(rc, rc); + Log4(("ecx=%#RX32 edx:eax=%#RX32:%#RX32\n", pMixedCtx->ecx, pMixedCtx->edx, pMixedCtx->eax)); + ++ /*MYCODE*/ ++ for(int iBreakpointId=0; iBreakpointIdCTX_SUFF(pVM)->bp.l[iBreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_MSRHBP ++ && pTempBreakpointEntrie->breakpointAccessType == FDP_WRITE_BP ++ && (pTempBreakpointEntrie->breakpointGCPtr == pMixedCtx->ecx || pTempBreakpointEntrie->breakpointGCPtr == 0)){ ++ pVCpu->mystate.s.bMsrHyperBreakPointHitted = true; ++ return VINF_EM_HALT; ++ } ++ } ++ /*ENDMYCODE*/ ++ ++ + rc = EMInterpretWrmsr(pVM, pVCpu, CPUMCTX2CORE(pMixedCtx)); + AssertMsg(rc == VINF_SUCCESS || rc == VERR_EM_INTERPRETER, ("hmR0VmxExitWrmsr: failed, invalid error code %Rrc\n", rc)); + STAM_COUNTER_INC(&pVCpu->hm.s.StatExitWrmsr); +@@ -12538,6 +12608,9 @@ HMVMX_EXIT_DECL hmR0VmxExitMovCRx(PVMCPU pVCpu, PCPUMCTX pMixedCtx, PVMXTRANSIEN + PVM pVM = pVCpu->CTX_SUFF(pVM); + VBOXSTRICTRC rcStrict; + rc = hmR0VmxSaveGuestRegsForIemExec(pVCpu, pMixedCtx, false /*fMemory*/, true /*fNeedRsp*/); ++ /*MYCODE*/ ++ bool bBreakpointHitted = false; ++ /*ENDMYCODE*/ + switch (uAccessType) + { + case VMX_EXIT_QUALIFICATION_CRX_ACCESS_WRITE: /* MOV to CRx */ +@@ -12580,7 +12653,22 @@ HMVMX_EXIT_DECL hmR0VmxExitMovCRx(PVMCPU pVCpu, PCPUMCTX pMixedCtx, PVMXTRANSIEN + } + + STAM_COUNTER_INC(&pVCpu->hm.s.StatExitCRxWrite[VMX_EXIT_QUALIFICATION_CRX_REGISTER(uExitQualification)]); +- break; ++ ++ /*MYCODE*/ ++ //Looking for a matching breakpoint ++ for(int iBreakpointId=0; iBreakpointIdCTX_SUFF(pVM)->bp.l[iBreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_CRHBP ++ && pTempBreakpointEntrie->breakpointAccessType == FDP_WRITE_BP ++ && (pTempBreakpointEntrie->breakpointGCPtr == VMX_EXIT_QUALIFICATION_CRX_REGISTER(uExitQualification))){ ++ bBreakpointHitted = true; ++ break; ++ } ++ } ++ /*ENDMYCODE*/ ++ ++ break; + } + + case VMX_EXIT_QUALIFICATION_CRX_ACCESS_READ: /* MOV from CRx */ +@@ -12640,6 +12728,14 @@ HMVMX_EXIT_DECL hmR0VmxExitMovCRx(PVMCPU pVCpu, PCPUMCTX pMixedCtx, PVMXTRANSIEN + + HMCPU_CF_SET(pVCpu, rcStrict != VINF_IEM_RAISED_XCPT ? HM_CHANGED_GUEST_RIP | HM_CHANGED_GUEST_RFLAGS : HM_CHANGED_ALL_GUEST); + STAM_PROFILE_ADV_STOP(&pVCpu->hm.s.StatExitMovCRx, y2); ++ ++ /*MYCODE*/ ++ if(bBreakpointHitted == true){ ++ pVCpu->mystate.s.bCrHyperBreakPointHitted = true; ++ return VINF_EM_HALT; ++ } ++ /*ENDMYCODE*/ ++ + NOREF(pVM); + return rcStrict; + } +@@ -13042,6 +13138,97 @@ HMVMX_EXIT_DECL hmR0VmxExitApicAccess(PVMCPU pVCpu, PCPUMCTX pMixedCtx, PVMXTRAN + */ + HMVMX_EXIT_DECL hmR0VmxExitMovDRx(PVMCPU pVCpu, PCPUMCTX pMixedCtx, PVMXTRANSIENT pVmxTransient) + { ++ ++ /*MYCODE*/ ++ { ++ int rc2; ++ rc2 = hmR0VmxReadExitQualificationVmcs(pVCpu, pVmxTransient); ++ rc2 |= hmR0VmxSaveGuestSegmentRegs(pVCpu, pMixedCtx); ++ rc2 |= hmR0VmxSaveGuestDR7(pVCpu, pMixedCtx); ++ AssertRCReturn(rc2, rc2); ++ ++ bool DRxWrite = false; ++ if (VMX_EXIT_QUALIFICATION_DRX_DIRECTION(pVmxTransient->uExitQualification) == VMX_EXIT_QUALIFICATION_DRX_DIRECTION_WRITE){ ++ DRxWrite = true; ++ } ++ ++ PVM pVM2 = pVCpu->CTX_SUFF(pVM); ++ ++ //Save the fakeDR to compare after the instruction ++ uint64_t aOldVisibleDr[8]; ++ aOldVisibleDr[0] = pVCpu->mystate.s.aGuestDr[0]; ++ aOldVisibleDr[1] = pVCpu->mystate.s.aGuestDr[1]; ++ aOldVisibleDr[2] = pVCpu->mystate.s.aGuestDr[2]; ++ aOldVisibleDr[3] = pVCpu->mystate.s.aGuestDr[3]; ++ aOldVisibleDr[7] = pVCpu->mystate.s.aGuestDr[7]; ++ ++ //TODO: not needed, we need to save the value when FDP_WriteRegister() ++ //Save Invisble Debug Register values used by HardHyperBreakpoint ++ uint64_t uInvisibleDr0 = ASMGetDR0(); ++ uint64_t uInvisibleDr1 = ASMGetDR1(); ++ uint64_t uInvisibleDr2 = ASMGetDR2(); ++ uint64_t uInvisibleDr3 = ASMGetDR3(); ++ uint64_t uInvisibleDr6 = ASMGetDR6(); ++ uint64_t uInvisibleDr7 = CPUMGetGuestDR7(pVCpu); ++ ++ //Load fake DR Values ++ CPUMSetHyperDR0(pVCpu, pVCpu->mystate.s.aGuestDr[0]); ++ CPUMSetHyperDR1(pVCpu, pVCpu->mystate.s.aGuestDr[1]); ++ CPUMSetHyperDR2(pVCpu, pVCpu->mystate.s.aGuestDr[2]); ++ CPUMSetHyperDR3(pVCpu, pVCpu->mystate.s.aGuestDr[3]); ++ CPUMSetHyperDR6(pVCpu, pVCpu->mystate.s.aGuestDr[6]); ++ VMXWriteVmcs32(VMX_VMCS_GUEST_DR7, (uint32_t)pVCpu->mystate.s.aGuestDr[7]); ++ ++ //Disable #MovDRx ++ pVCpu->hm.s.vmx.u32ProcCtls &= ~VMX_VMCS_CTRL_PROC_EXEC_MOV_DR_EXIT; ++ //Enable MTF ++ pVCpu->hm.s.vmx.u32ProcCtls |= VMX_VMCS_CTRL_PROC_EXEC_MONITOR_TRAP_FLAG; ++ rc2 = VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.u32ProcCtls); ++ ++ //Single Step ++ hmR0VmxRunGuestCodeNormal(pVM2, pVCpu, pMixedCtx); ++ rc2 = hmR0VmxReadExitQualificationVmcs(pVCpu, pVmxTransient); ++ rc2 |= hmR0VmxSaveGuestSegmentRegs(pVCpu, pMixedCtx); ++ rc2 |= hmR0VmxSaveGuestDR7(pVCpu, pMixedCtx); ++ ++ //Disable MTF ++ pVCpu->hm.s.vmx.u32ProcCtls &= ~VMX_VMCS_CTRL_PROC_EXEC_MONITOR_TRAP_FLAG; ++ //Enable #MovDrx ++ pVCpu->hm.s.vmx.u32ProcCtls |= VMX_VMCS_CTRL_PROC_EXEC_MOV_DR_EXIT; ++ rc2 = VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.u32ProcCtls); ++ ++ //Save new Visible Debug Register values (Usefull only on write) ++ pVCpu->mystate.s.aGuestDr[0] = ASMGetDR0(); ++ pVCpu->mystate.s.aGuestDr[1] = ASMGetDR1(); ++ pVCpu->mystate.s.aGuestDr[2] = ASMGetDR2(); ++ pVCpu->mystate.s.aGuestDr[3] = ASMGetDR3(); ++ pVCpu->mystate.s.aGuestDr[6] = ASMGetDR6(); ++ pVCpu->mystate.s.aGuestDr[7] = pMixedCtx->dr[7]; ++ ++ //Restore Invisible Debug Register values ++ CPUMSetHyperDR0(pVCpu, uInvisibleDr0); ++ CPUMSetHyperDR1(pVCpu, uInvisibleDr1); ++ CPUMSetHyperDR2(pVCpu, uInvisibleDr2); ++ CPUMSetHyperDR3(pVCpu, uInvisibleDr3); ++ CPUMSetHyperDR6(pVCpu, uInvisibleDr6); ++ CPUMSetGuestDR7(pVCpu, (uint32_t)uInvisibleDr7); ++ VMXWriteVmcs32(VMX_VMCS_GUEST_DR7, (uint32_t)uInvisibleDr7); ++ ++ //If a Visible Debug Register changed go to ring-3 install/remove a breakpoint ++ if (aOldVisibleDr[0] != pVCpu->mystate.s.aGuestDr[0] ++ || aOldVisibleDr[1] != pVCpu->mystate.s.aGuestDr[1] ++ || aOldVisibleDr[2] != pVCpu->mystate.s.aGuestDr[2] ++ || aOldVisibleDr[3] != pVCpu->mystate.s.aGuestDr[3] ++ || aOldVisibleDr[7] != pVCpu->mystate.s.aGuestDr[7]){ ++ pVCpu->mystate.s.bInstallDrBreakpointRequired = true; ++ return VINF_EM_HALT; ++ } ++ //Go ! ++ return VINF_SUCCESS; ++ } ++ /*ENDMYCODE*/ ++ ++ + HMVMX_VALIDATE_EXIT_HANDLER_PARAMS(); + + /* We should -not- get this VM-exit if the guest's debug registers were active. */ +@@ -13199,6 +13386,8 @@ HMVMX_EXIT_DECL hmR0VmxExitEptViolation(PVMCPU pVCpu, PCPUMCTX pMixedCtx, PVMXTR + HMVMX_VALIDATE_EXIT_HANDLER_PARAMS(); + Assert(pVCpu->CTX_SUFF(pVM)->hm.s.fNestedPaging); + ++ ++ + /* If this VM-exit occurred while delivering an event through the guest IDT, handle it accordingly. */ + VBOXSTRICTRC rcStrict1 = hmR0VmxCheckExitDueToEventDelivery(pVCpu, pMixedCtx, pVmxTransient); + if (RT_LIKELY(rcStrict1 == VINF_SUCCESS)) +@@ -13248,6 +13437,172 @@ HMVMX_EXIT_DECL hmR0VmxExitEptViolation(PVMCPU pVCpu, PCPUMCTX pMixedCtx, PVMXTR + VBOXSTRICTRC rcStrict2 = PGMR0Trap0eHandlerNestedPaging(pVM, pVCpu, PGMMODE_EPT, uErrorCode, CPUMCTX2CORE(pMixedCtx), GCPhys); + TRPMResetTrap(pVCpu); + ++ /*MYCODE*/ ++ if(PGMShwIsBreakable(pVCpu, GCPhys) == true){ ++ PHMGLOBALCPUINFO pCpu = hmR0GetCurrentCpu(); ++ STAM_COUNTER_INC(&pVCpu->hm.s.StatExitGuestDE); ++ if(VMMGetBreakpointIdFromPage(pVM, GCPhys, FDP_PAGEHBP) >= 0){ //FDP_PAGEHBP ++ HMCPU_CF_SET(pVCpu, HM_CHANGED_GUEST_RIP ++ | HM_CHANGED_GUEST_RSP ++ | HM_CHANGED_GUEST_RFLAGS); ++ ++ int tmpAccess = 0x00; ++ if(pVmxTransient->uExitQualification & VMX_EXIT_QUALIFICATION_EPT_DATA_READ) ++ tmpAccess |= (int)FDP_READ_BP; ++ if(pVmxTransient->uExitQualification & VMX_EXIT_QUALIFICATION_EPT_DATA_WRITE) ++ tmpAccess |= (int)FDP_WRITE_BP; ++ if(pVmxTransient->uExitQualification & VMX_EXIT_QUALIFICATION_EPT_INSTR_FETCH) ++ tmpAccess |= (int)FDP_EXECUTE_BP; ++ ++ ++ STAM_COUNTER_INC(&pVCpu->hm.s.StatExitGuestDE); ++ //If it is one of our breakpoints, go to VMMR3 ! ++ int PageBreakpointId = VMMGetBreakpointId(pVM, GCPhys, FDP_PAGEHBP, tmpAccess); ++ if(PageBreakpointId >= (int)(4*pVM->cCpus)){ ++ //This is a host page breakpoint ! ++ pVCpu->mystate.s.bPageHyperBreakPointHitted = true; ++ ++ //RTSpinlockAcquire(pVM->mystate.s.PageSpinlock); ++ PGMShwRestoreRights(pVCpu, GCPhys); ++ VMXR0InvalidatePhysPage(pVM, pVCpu, GCPhys); ++ //RTSpinlockRelease(pVM->mystate.s.PageSpinlock); ++ ++ return VINF_EM_HALT; ++ } ++ ++ if(PageBreakpointId >= 0 ++ && PageBreakpointId < (int)(4*pVM->cCpus)){ ++ STAM_COUNTER_INC(&pVCpu->hm.s.StatExitGuestGP); ++ //This is a Guest Hardware Breakpoint ! ++ //Update the guest dr6 ++ pMixedCtx->dr[6] = pVCpu->mystate.s.aGuestDr[6]; ++ for(int i=0; i<4; i++){ ++ if(VMMMatchBreakpointId(pVM, i, GCPhys, FDP_PAGEHBP, tmpAccess)){ ++ pMixedCtx->dr[6] = pMixedCtx->dr[6] | ((uint64_t)(0x1 << (i))); ++ } ++ } ++ ASMSetDR6(pMixedCtx->dr[6]); ++ ++ //Inject a INT1 into the guest ++ hmR0VmxSetPendingXcptDB(pVCpu, pMixedCtx); ++ return VINF_SUCCESS; ++ } ++ ++ //If it not the breakpoint then continue ! ++ //RTSpinlockAcquire(pVM->mystate.s.PageSpinlock); ++ PGMShwPresent(pVCpu, GCPhys); ++ PGMShwWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); ++ //Flush TLB ++#ifdef IN_RING0 ++ hmR0VmxFlushTaggedTlb(pVCpu, pCpu); ++#endif ++ ++ //Active MTF ++ pVCpu->hm.s.vmx.u32ProcCtls |= VMX_VMCS_CTRL_PROC_EXEC_MONITOR_TRAP_FLAG; ++ int rc3 = VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.u32ProcCtls); ++ ++ //Single Step ++ hmR0VmxRunGuestCodeNormal(pVM, pVCpu, pMixedCtx); ++ ++ //Disable MTF ++ pVCpu->hm.s.vmx.u32ProcCtls &= ~VMX_VMCS_CTRL_PROC_EXEC_MONITOR_TRAP_FLAG; ++ rc3 = VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.u32ProcCtls); ++ ++ //TODO: restoreOldFlags ++ PGMShwRestoreRights(pVCpu, GCPhys); ++ //Flush TLB ++ VMXR0InvalidatePhysPage(pVM, pVCpu, GCPhys); ++ //RTSpinlockRelease(pVM->mystate.s.PageSpinlock); ++ ++ return VINF_SUCCESS; ++ } ++ int SoftBreakpointId = VMMGetBreakpointIdFromPage(pVM, GCPhys, FDP_SOFTHBP); ++ if(SoftBreakpointId > 0){ ++ //FDP_SOFTHBP ++ HMCPU_CF_SET(pVCpu, HM_CHANGED_GUEST_RIP ++ | HM_CHANGED_GUEST_RSP ++ | HM_CHANGED_GUEST_RFLAGS); ++ ++ //Avoid stack overflow when Fault inside fault ! ++ if(pVCpu->mystate.s.bPageFaultOverflowGuard){ ++ STAM_COUNTER_INC(&pVCpu->hm.s.StatExitGuestGP); ++ return VINF_SUCCESS; ++ } ++ ++ //RTSpinlockAcquire(pVM->mystate.s.PageSpinlock); ++ pVCpu->mystate.s.bPageFaultOverflowGuard = true; ++ bool bWriteAccess = ((pVmxTransient->uExitQualification & VMX_EXIT_QUALIFICATION_EPT_DATA_WRITE) != 0); ++ rc = VINF_SUCCESS; ++ ++ //Execute Access ++ if(pVmxTransient->uExitQualification & VMX_EXIT_QUALIFICATION_EPT_INSTR_FETCH) ++ { ++ //Execute only on ModPage ++ PGMShwSetHCPage(pVCpu, GCPhys, pVM->bp.l[SoftBreakpointId].breakpointHardwarePage->HCPhys); ++ PGMShwNoPresent(pVCpu, GCPhys); ++ PGMShwNoWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); //Execute ! ++ }else { ++ //Read or Write Access ++ //if((pVmxTransient->uExitQualification & VMX_EXIT_QUALIFICATION_EPT_DATA_READ) ++ // || (pVmxTransient->uExitQualification & VMX_EXIT_QUALIFICATION_EPT_DATA_WRITE)) ++ //{ ++ //Read, Write on OriginalPage ++ PGMShwSetHCPage(pVCpu, GCPhys, pVM->bp.l[SoftBreakpointId].breakpointOrigHCPhys); ++ PGMShwPresent(pVCpu, GCPhys); //Read ! ++ PGMShwWrite(pVCpu, GCPhys); //Write ! ++ PGMShwNoExecute(pVCpu, GCPhys); ++ //} ++ } ++ ++ //Trash case, TODO: What is this ? Why ? Maybe "mov [rax], rcx" and rax inside the page ++ if( ++ ((pVmxTransient->uExitQualification & VMX_EXIT_QUALIFICATION_EPT_DATA_READ) && (pVmxTransient->uExitQualification & VMX_EXIT_QUALIFICATION_EPT_DATA_WRITE) && (pVmxTransient->uExitQualification & VMX_EXIT_QUALIFICATION_EPT_INSTR_FETCH)) ++ ) ++ { //Special case ++ //Full rights on OriginalPage ++ PGMShwSetHCPage(pVCpu, GCPhys, pVM->bp.l[SoftBreakpointId].breakpointOrigHCPhys); ++ PGMShwPresent(pVCpu, GCPhys); ++ PGMShwWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); ++ //Invalidate the GPA ++ VMXR0InvalidatePhysPage(pVM, pVCpu, GCPhys); ++ ++ //Active MTF ++ pVCpu->hm.s.vmx.u32ProcCtls |= VMX_VMCS_CTRL_PROC_EXEC_MONITOR_TRAP_FLAG; ++ VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.u32ProcCtls); ++ ++ //Single Step ++ rc = VBOXSTRICTRC_VAL(hmR0VmxRunGuestCodeNormal(pVM, pVCpu, pMixedCtx)); ++ ++ //Disable MTF ++ pVCpu->hm.s.vmx.u32ProcCtls &= ~VMX_VMCS_CTRL_PROC_EXEC_MONITOR_TRAP_FLAG; ++ VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.u32ProcCtls); ++ ++ //Execute only on ModPage ++ PGMShwSetHCPage(pVCpu, GCPhys, pVM->bp.l[SoftBreakpointId].breakpointHardwarePage->HCPhys); ++ PGMShwNoPresent(pVCpu, GCPhys); ++ PGMShwNoWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); //Execute ! ++ }else{ ++ if(bWriteAccess == true){ ++ //TODO: OrignalPage, SingleStep, Copy OrignalPage to ModPage, Reinstall the HLT ++ } ++ } ++ ++ //Invalidate the page ++ VMXR0InvalidatePhysPage(pVM, pVCpu, GCPhys); ++ ++ pVCpu->mystate.s.bPageFaultOverflowGuard = false; ++ //RTSpinlockRelease(pVM->mystate.s.PageSpinlock); ++ ++ return rc; ++ } ++ } ++ /*ENDMYCODE*/ ++ ++ + /* Same case as PGMR0Trap0eHandlerNPMisconfig(). See comment above, @bugref{6043}. */ + if ( rcStrict2 == VINF_SUCCESS + || rcStrict2 == VERR_PAGE_TABLE_NOT_PRESENT +@@ -13318,6 +13673,56 @@ static int hmR0VmxExitXcptBP(PVMCPU pVCpu, PCPUMCTX pMixedCtx, PVMXTRANSIENT pVm + int rc = hmR0VmxSaveGuestState(pVCpu, pMixedCtx); + AssertRCReturn(rc, rc); + ++ /*MYCODE*/ ++ { ++ int rc2 = hmR0VmxReadExitIntInfoVmcs(pVmxTransient); ++ rc2 |= hmR0VmxReadExitIntErrorCodeVmcs(pVmxTransient); ++ rc2 |= hmR0VmxReadExitInstrLenVmcs(pVmxTransient); ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ uint64_t GCPhys; ++ PGMPhysGCPtr2GCPhys(pVCpu, pMixedCtx->rip, &GCPhys); ++ int SoftBreakpointId = VMMGetBreakpointId(pVM, GCPhys, FDP_SOFTHBP, FDP_EXECUTE_BP); ++ if(SoftBreakpointId >= 0){ ++ if(pVM->bp.l[SoftBreakpointId].breakpointCr3 == 0 ++ || pVM->bp.l[SoftBreakpointId].breakpointCr3 == CPUMGetGuestCR3(pVCpu)){ ++ pVCpu->mystate.s.bSoftHyperBreakPointHitted = true; ++ return VINF_EM_HALT; ++ }else{ ++ //This breakpoint is filtered ++ //Full rights on OriginalPage ++ PGMShwSetHCPage(pVCpu, GCPhys, pVM->bp.l[SoftBreakpointId].breakpointOrigHCPhys); ++ PGMShwPresent(pVCpu, GCPhys); ++ PGMShwWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); ++ //Invalidate the GPA ++ VMXR0InvalidatePhysPage(pVM, pVCpu, GCPhys); ++ ++ //Enable MTF ++ pVCpu->hm.s.vmx.u32ProcCtls |= VMX_VMCS_CTRL_PROC_EXEC_MONITOR_TRAP_FLAG; ++ rc2 = VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.u32ProcCtls); ++ ++ //Single Step ++ hmR0VmxRunGuestCodeNormal(pVM, pVCpu, pMixedCtx); ++ rc2 = hmR0VmxReadExitQualificationVmcs(pVCpu, pVmxTransient); ++ rc2 |= hmR0VmxSaveGuestSegmentRegs(pVCpu, pMixedCtx); ++ rc2 |= hmR0VmxSaveGuestDR7(pVCpu, pMixedCtx); ++ ++ //Disable MTF ++ pVCpu->hm.s.vmx.u32ProcCtls &= ~VMX_VMCS_CTRL_PROC_EXEC_MONITOR_TRAP_FLAG; ++ ++ //Execute only on ModPage ++ PGMShwSetHCPage(pVCpu, GCPhys, pVM->bp.l[SoftBreakpointId].breakpointHardwarePage->HCPhys); ++ PGMShwNoPresent(pVCpu, GCPhys); ++ PGMShwNoWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); //Execute ! ++ return VINF_SUCCESS; ++ } ++ } ++ } ++ /*ENDMYCODE*/ ++ ++ ++ + PVM pVM = pVCpu->CTX_SUFF(pVM); + rc = DBGFRZTrap03Handler(pVM, pVCpu, CPUMCTX2CORE(pMixedCtx)); + if (rc == VINF_EM_RAW_GUEST_TRAP) +@@ -13379,6 +13784,28 @@ static int hmR0VmxExitXcptDB(PVMCPU pVCpu, PCPUMCTX pMixedCtx, PVMXTRANSIENT pVm + uDR6 |= ( pVmxTransient->uExitQualification + & (X86_DR6_B0 | X86_DR6_B1 | X86_DR6_B2 | X86_DR6_B3 | X86_DR6_BD | X86_DR6_BS)); + ++ /*MYCODE*/ ++ { ++ //If it is a breakpoint we handle it ++ if((uDR6 & (X86_DR6_B0 | X86_DR6_B1 | X86_DR6_B2 | X86_DR6_B3))){ ++ //Update DR6 ! ++ VMMRZCallRing3Disable(pVCpu); ++ HM_DISABLE_PREEMPT(); ++ ++ pMixedCtx->dr[6] &= ~X86_DR6_B_MASK; ++ pMixedCtx->dr[6] |= uDR6; ++ if (CPUMIsGuestDebugStateActive(pVCpu)) ++ ASMSetDR6(pMixedCtx->dr[6]); ++ ++ HM_RESTORE_PREEMPT(); ++ VMMRZCallRing3Enable(pVCpu); ++ ++ pVCpu->mystate.s.bHardHyperBreakPointHitted = true; ++ return VINF_EM_HALT; ++ } ++ } ++ /*ENDMYCODE*/ ++ + rc = DBGFRZTrap01Handler(pVCpu->CTX_SUFF(pVM), pVCpu, CPUMCTX2CORE(pMixedCtx), uDR6, pVCpu->hm.s.fSingleInstruction); + if (rc == VINF_EM_RAW_GUEST_TRAP) + { +diff --git a/src/VBox/VMM/VMMR0/VMMR0.cpp b/src/VBox/VMM/VMMR0/VMMR0.cpp +index 2fe15e37..36438757 100644 +--- a/src/VBox/VMM/VMMR0/VMMR0.cpp ++++ b/src/VBox/VMM/VMMR0/VMMR0.cpp +@@ -56,6 +56,14 @@ + #include + #include + ++/*MYCODE*/ ++#include ++#include ++#include ++#include ++/*ENDMYCODE*/ ++ ++ + #include "dtrace/VBoxVMM.h" + + +@@ -1981,6 +1989,47 @@ static int vmmR0EntryExWorker(PGVM pGVM, PVM pVM, VMCPUID idCpu, VMMR0OPERATION + VMM_CHECK_SMAP_CHECK2(pVM, RT_NOTHING); + break; + #endif ++ /*MYCODE*/ ++ case VMMR0_DO_ALLOC_HCPHYS: ++ { ++ ALLOCPAGEREQ* pReq = (ALLOCPAGEREQ*)pReqHdr; ++ if(pReq == NULL){ ++ LogRel(("[WDEBUG] pReqHdr is NULL\n")); ++ return -2; ++ } ++ int rc = 0; ++ RTR0MEMOBJ hMemObjMod; ++ //Allocates a new physical page ++ //rc = RTR0MemObjAllocPhysEx(&hMemObjMod, pReq->newPageSize, NIL_RTHCPHYS, pReq->newPageSize); ++ //rc = RTR0MemObjAllocPhysEx(&hMemObjMod, 4096, NIL_RTHCPHYS, 4096); ++ rc = RTR0MemObjAllocLow(&hMemObjMod, pReq->newPageSize, false); ++ if(RT_SUCCESS(rc)){ ++ //Maps the new page in ring-0 address space ++ RTR0MEMOBJ hMapObjMod; ++ rc = RTR0MemObjMapKernel(&hMapObjMod, hMemObjMod, (void *)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE); ++ //Maps the new page in ring-3 address space ++ int rc2 = RTR0MemObjMapUser(&hMapObjMod, hMemObjMod, (RTR3PTR)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE, NIL_RTR0PROCESS); ++ LogRel(("[WEBUG] Mappin %d %d\n", rc, rc2)); ++ if(RT_SUCCESS(rc) && RT_SUCCESS(rc2)){ ++ //Gets the ring-0 address of the new page ++ //pVM->args.allochcphysreq.R0Ptr = (uint8_t*)RTR0MemObjAddress(hMapObjMod); ++ //Gets the ring-3 address of the new page ++ pReq->newPageR3Ptr = (uint8_t*)RTR0MemObjAddressR3(hMapObjMod); ++ //Gets the physical address of the new page ++ pReq->newPageHCPHys = RTR0MemObjGetPagePhysAddr(hMapObjMod, 0); ++ return 0; ++ }else{ ++ LogRel(("[WDEBUG] Map failed ! \n")); ++ return -3; ++ } ++ }else{ ++ LogRel(("[WDEBUG] Alloc failed !\n")); ++ return rc; ++ } ++ return -4; ++ } ++ return VERR_NOT_SUPPORTED; ++ /*ENMYCODE*/ + default: + /* + * We're returning VERR_NOT_SUPPORT here so we've got something else +diff --git a/src/VBox/VMM/VMMR3/CPUM.cpp b/src/VBox/VMM/VMMR3/CPUM.cpp +index ffb39cad..646557d1 100644 +--- a/src/VBox/VMM/VMMR3/CPUM.cpp ++++ b/src/VBox/VMM/VMMR3/CPUM.cpp +@@ -1141,6 +1141,12 @@ VMMR3DECL(int) CPUMR3Term(PVM pVM) + */ + VMMR3DECL(void) CPUMR3ResetCpu(PVM pVM, PVMCPU pVCpu) + { ++ ++ /*MYCODE*/ ++ pVCpu->mystate.s.bRestoreRequired = false; ++ pVCpu->mystate.s.bPauseRequired = false; ++ /*ENDMYCODE*/ ++ + /** @todo anything different for VCPU > 0? */ + PCPUMCTX pCtx = &pVCpu->cpum.s.Guest; + +diff --git a/src/VBox/VMM/VMMR3/EM.cpp b/src/VBox/VMM/VMMR3/EM.cpp +index e5e6c0b7..7b920b2f 100644 +--- a/src/VBox/VMM/VMMR3/EM.cpp ++++ b/src/VBox/VMM/VMMR3/EM.cpp +@@ -71,6 +71,9 @@ + #include + #include + ++/*MYCODE*/ ++#include ++/*ENDMYCODE*/ + + /********************************************************************************************************************************* + * Defined Constants And Macros * +@@ -147,6 +150,12 @@ VMMR3_INT_DECL(int) EMR3Init(PVM pVM) + pVM->em.s.fGuruOnTripleFault = true; + } + ++ /*MYCODE*/ ++ //Dont Guru on Triple Fault... This is annoying ! ++ pVM->em.s.fGuruOnTripleFault = false; ++ /*ENDMYCODE*/ ++ ++ + Log(("EMR3Init: fRecompileUser=%RTbool fRecompileSupervisor=%RTbool fRawRing1Enabled=%RTbool fIemExecutesAll=%RTbool fGuruOnTripleFault=%RTbool\n", + pVM->fRecompileUser, pVM->fRecompileSupervisor, pVM->fRawRing1Enabled, pVM->em.s.fIemExecutesAll, pVM->em.s.fGuruOnTripleFault)); + +@@ -844,8 +853,10 @@ static VBOXSTRICTRC emR3Debug(PVM pVM, PVMCPU pVCpu, VBOXSTRICTRC rc) + * Simple events: stepped, breakpoint, stop/assertion. + */ + case VINF_EM_DBG_STEPPED: +- rc = DBGFR3Event(pVM, DBGFEVENT_STEPPED); +- break; ++ /*MYCODE*/ ++ //rc = DBGFR3Event(pVM, DBGFEVENT_STEPPED); ++ /*ENDMYCODE*/ ++ break; + + case VINF_EM_DBG_BREAKPOINT: + rc = DBGFR3EventBreakpoint(pVM, DBGFEVENT_BREAKPOINT); +@@ -2293,6 +2304,29 @@ VMMR3_INT_DECL(int) EMR3ExecuteVM(PVM pVM, PVMCPU pVCpu) + else if (fFFDone) + fFFDone = false; + ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired){ ++ //Set the active CPU as STATE_PAUSED ++ pVCpu->mystate.s.u8StateBitmap |= FDP_STATE_PAUSED; ++ //LogRel(("[WDEBUG] CPU[%d] Entering PAUSE in EM!\n", pVCpu->idCpu)); ++ VMR3EnterPause(pVM, pVCpu); ++ //LogRel(("[WDEBUG] CPU[%d] Leaving PAUSE in EM!\n", pVCpu->idCpu)); ++ rc = VINF_SUCCESS; ++ } ++ pVCpu->mystate.s.u8StateBitmap &= ~FDP_STATE_PAUSED; ++ pVCpu->mystate.s.u64TickCount++; ++ if(pVCpu->mystate.s.bRebootRequired){ ++ rc = VINF_EM_TRIPLE_FAULT; ++ pVM->em.s.fGuruOnTripleFault = false; ++ pVCpu->mystate.s.bRebootRequired = false; ++ } ++ if(pVCpu->mystate.s.bSuspendRequired){ ++ rc = VINF_EM_SUSPEND; ++ pVCpu->mystate.s.bSuspendRequired = false; ++ } ++ /*MYCODE*/ ++ ++ + /* + * Now what to do? + */ +@@ -2807,6 +2841,22 @@ VMMR3_INT_DECL(int) EMR3ExecuteVM(PVM pVM, PVMCPU pVCpu) + /* not reached */ + } + ++/*MYCODE*/ ++VMMR3_INT_DECL(int) EMR3ProcessForcedAction(PVM pVM, PVMCPU pVCpu, int rc) ++{ ++ rc = emR3ForcedActions(pVM, pVCpu, rc); ++ VBOXVMM_EM_FF_ALL_RET(pVCpu, rc); ++ ++ EMSTATE enmState = emR3Reschedule(pVM, pVCpu, pVCpu->em.s.pCtx); ++ //LogRel(("EMR3ExecuteVM: VINF_EM_RESCHEDULE: %d -> %d (%s)\n", 0, enmState, "emR3GetStateName(enmState)")); ++ if (pVCpu->em.s.enmState != enmState && enmState == EMSTATE_IEM_THEN_REM) ++ pVCpu->em.s.cIemThenRemInstructions = 0; ++ pVCpu->em.s.enmState = enmState; ++ return rc; ++} ++/*ENDMYCODE*/ ++ ++ + /** + * Notify EM of a state change (used by FTM) + * +diff --git a/src/VBox/VMM/VMMR3/EMHM.cpp b/src/VBox/VMM/VMMR3/EMHM.cpp +index 5effe032..2b34113b 100644 +--- a/src/VBox/VMM/VMMR3/EMHM.cpp ++++ b/src/VBox/VMM/VMMR3/EMHM.cpp +@@ -497,6 +497,12 @@ int emR3HmExecute(PVM pVM, PVMCPU pVCpu, bool *pfFFDone) + break; + } + } ++ ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired) ++ break; ++ /*ENDMYCODE*/ ++ + } + + /* +diff --git a/src/VBox/VMM/VMMR3/MM.cpp b/src/VBox/VMM/VMMR3/MM.cpp +index c28e1e83..e33aca8f 100644 +--- a/src/VBox/VMM/VMMR3/MM.cpp ++++ b/src/VBox/VMM/VMMR3/MM.cpp +@@ -854,3 +854,8 @@ VMMR3DECL(uint32_t) MMR3PhysGet4GBRamHoleSize(PVM pVM) + return pVM->mm.s.cbRamHole; + } + ++ ++VMMR3DECL(uint64_t) MMR3PhysGetRamSizeU(PUVM pUVM) ++{ ++ return pUVM->pVM->mm.s.cbRamBase; ++} +diff --git a/src/VBox/VMM/VMMR3/PDM.cpp b/src/VBox/VMM/VMMR3/PDM.cpp +index 98f61579..fdd6528e 100644 +--- a/src/VBox/VMM/VMMR3/PDM.cpp ++++ b/src/VBox/VMM/VMMR3/PDM.cpp +@@ -2037,7 +2037,7 @@ DECLINLINE(int) pdmR3ResumeDev(PPDMDEVINS pDevIns) + */ + VMMR3_INT_DECL(void) PDMR3Resume(PVM pVM) + { +- LogFlow(("PDMR3Resume:\n")); ++ LogRel(("PDMR3Resume:\n")); + + /* + * Iterate thru the device instances and USB device instances, +diff --git a/src/VBox/VMM/VMMR3/PGMDbg.cpp b/src/VBox/VMM/VMMR3/PGMDbg.cpp +index 143e6878..b4afb504 100644 +--- a/src/VBox/VMM/VMMR3/PGMDbg.cpp ++++ b/src/VBox/VMM/VMMR3/PGMDbg.cpp +@@ -779,6 +779,10 @@ VMMR3_INT_DECL(int) PGMR3DbgScanPhysical(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cbRa + return VERR_DBGF_MEM_NOT_FOUND; + } + ++VMMR3_INT_DECL(int) PGMR3DbgScanPhysicalU(PUVM pUVM, RTGCPHYS GCPhys, RTGCPHYS cbRange, RTGCPHYS GCPhysAlign, const uint8_t *pabNeedle, size_t cbNeedle, PRTGCPHYS pGCPhysHit) ++{ ++ return PGMR3DbgScanPhysical(pUVM->pVM, GCPhys, cbRange, GCPhysAlign, pabNeedle, cbNeedle, pGCPhysHit); ++} + + /** + * Scans (guest) virtual memory for a byte string. +diff --git a/src/VBox/VMM/VMMR3/TRPM.cpp b/src/VBox/VMM/VMMR3/TRPM.cpp +index bc59b7b9..a14d3a6c 100644 +--- a/src/VBox/VMM/VMMR3/TRPM.cpp ++++ b/src/VBox/VMM/VMMR3/TRPM.cpp +@@ -1496,6 +1496,17 @@ VMMR3DECL(bool) TRPMR3IsGateHandler(PVM pVM, RTRCPTR GCPtr) + */ + VMMR3DECL(int) TRPMR3InjectEvent(PVM pVM, PVMCPU pVCpu, TRPMEVENT enmEvent) + { ++ ++ /*MYCODE*/ ++ //Avoid interrupt during restore or pause ++ if(pVCpu->mystate.s.bRestoreRequired ++ || pVCpu->mystate.s.bPauseRequired ++ || pVCpu->mystate.s.bDisableInterrupt){ ++ return VINF_EM_RESCHEDULE_HM; ++ } ++ /*ENDMYCODE*/ ++ ++ + #ifdef VBOX_WITH_RAW_MODE + PCPUMCTX pCtx = CPUMQueryGuestCtxPtr(pVCpu); + Assert(!PATMIsPatchGCAddr(pVM, pCtx->eip)); +diff --git a/src/VBox/VMM/VMMR3/VM.cpp b/src/VBox/VMM/VMMR3/VM.cpp +index 65904ee0..9d54a134 100644 +--- a/src/VBox/VMM/VMMR3/VM.cpp ++++ b/src/VBox/VMM/VMMR3/VM.cpp +@@ -92,6 +92,11 @@ + #include + #include + ++/*MYCODE*/ ++#include ++#include ++#include ++/*ENDMYCODE*/ + + /********************************************************************************************************************************* + * Internal Functions * +@@ -655,6 +660,22 @@ static int vmR3CreateU(PUVM pUVM, uint32_t cCpus, PFNCFGMCONSTRUCTOR pfnCFGMCons + rc = vmR3ReadBaseConfig(pVM, pUVM, cCpus); + if (RT_SUCCESS(rc)) + { ++ ++ /*MYCODE*/ ++ //This spinlock is created in Ring-0! ++ strcpy(pVM->mystate.s.PageSpinLockName, "PAGELOCK_"); ++ strcat(pVM->mystate.s.PageSpinLockName, VMR3GetName(pUVM)); ++ ++ //Create CpuSpinLock ++ char CpuSpinLockName[256]; ++ strcpy(CpuSpinLockName, "CPULOCK_"); ++ strcat(CpuSpinLockName, VMR3GetName(pUVM)); ++ pVM->mystate.s.CpuLock = NIL_RTSPINLOCK; ++ RTSpinlockCreate(&pVM->mystate.s.CpuLock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, CpuSpinLockName); ++ /*ENDMYCODE*/ ++ ++ ++ + /* + * Init the ring-3 components and ring-3 per cpu data, finishing it off + * by a relocation round (intermediate context finalization will do this). +@@ -1482,6 +1503,14 @@ static DECLCALLBACK(VBOXSTRICTRC) vmR3Resume(PVM pVM, PVMCPU pVCpu, void *pvUser + VMRESUMEREASON enmReason = (VMRESUMEREASON)(uintptr_t)pvUser; + LogFlow(("vmR3Resume: pVM=%p pVCpu=%p/#%u enmReason=%d\n", pVM, pVCpu, pVCpu->idCpu, enmReason)); + ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bRestoreRequired == true){ ++ pVCpu->mystate.s.bPauseRequired = true; ++ } ++ /*ENDMYCODE*/ ++ ++ ++ + /* + * The first thread thru here tries to change the state. We shouldn't be + * called again if this fails. +@@ -2381,6 +2410,28 @@ static DECLCALLBACK(VBOXSTRICTRC) vmR3PowerOff(PVM pVM, PVMCPU pVCpu, void *pvUs + */ + VMMR3DECL(int) VMR3PowerOff(PUVM pUVM) + { ++ ++ /*MYCODE*/ ++ PVMCPU pVCpu = &pUVM->pVM->aCpus[0]; ++ //Do this only if the vCpu is Paused ++ if(pVCpu->mystate.s.bPauseRequired == true){ ++ //Avoid freeze ++ //VMR3Break(pUVM); ++ //Clear All Breakpoint ++ for(int BreakpointId=0; BreakpointIdmystate.s.bPauseRequired = false; ++ ++ //Stop FDP Debugger ++ FDP_SHM *pFdpShm = (FDP_SHM *)pUVM->pVM->mystate.s.pFdpShm; ++ pFdpShm->pFdpServer->bIsRunning = false; ++ } ++ /*ENDMYCODE*/ ++ + LogFlow(("VMR3PowerOff: pUVM=%p\n", pUVM)); + UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); + PVM pVM = pUVM->pVM; +diff --git a/src/VBox/VMM/VMMR3/VMEmt.cpp b/src/VBox/VMM/VMMR3/VMEmt.cpp +index 46db8e50..e0fd715e 100644 +--- a/src/VBox/VMM/VMMR3/VMEmt.cpp ++++ b/src/VBox/VMM/VMMR3/VMEmt.cpp +@@ -42,6 +42,17 @@ + #include + #include + ++/*MYCODE*/ ++#include ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++/*ENDMYCODE*/ + + /********************************************************************************************************************************* + * Internal Functions * +@@ -373,12 +384,24 @@ static DECLCALLBACK(int) vmR3HaltOldDoHalt(PUVMCPU pUVCpu, const uint32_t fMask, + if ( VM_FF_IS_PENDING(pVM, VM_FF_EXTERNAL_HALTED_MASK) + || VMCPU_FF_IS_PENDING(pVCpu, fMask)) + break; ++ ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired == true) ++ break; ++ /*ENDMYCODE*/ ++ ++ + uint64_t u64NanoTS; + TMTimerPollGIP(pVM, pVCpu, &u64NanoTS); + if ( VM_FF_IS_PENDING(pVM, VM_FF_EXTERNAL_HALTED_MASK) + || VMCPU_FF_IS_PENDING(pVCpu, fMask)) + break; + ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired == true) ++ break; ++ /*ENDMYCODE*/ ++ + /* + * Wait for a while. Someone will wake us up or interrupt the call if + * anything needs our attention. +@@ -722,6 +745,11 @@ static DECLCALLBACK(int) vmR3HaltGlobal1Halt(PUVMCPU pUVCpu, const uint32_t fMas + || VMCPU_FF_IS_PENDING(pVCpu, fMask)) + break; + ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired == true) ++ break; ++ /*ENDMYCODE*/ ++ + /* + * Estimate time left to the next event. + */ +@@ -732,6 +760,11 @@ static DECLCALLBACK(int) vmR3HaltGlobal1Halt(PUVMCPU pUVCpu, const uint32_t fMas + || VMCPU_FF_IS_PENDING(pVCpu, fMask)) + break; + ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired == true) ++ break; ++ /*ENDMYCODE*/ ++ + /* + * Block if we're not spinning and the interval isn't all that small. + */ +@@ -1069,6 +1102,925 @@ VMMR3_INT_DECL(void) VMR3NotifyCpuFFU(PUVMCPU pUVCpu, uint32_t fFlags) + g_aHaltMethods[pUVM->vm.s.iHaltMethod].pfnNotifyCpuFF(pUVCpu, fFlags); + } + ++/*MYCODE*/ ++//TODO: include with DBGTcp.cpp ++#define DEBUG_LEVEL 1 ++ ++#if DEBUG_LEVEL > 0 ++#define LogRelDebug(x) LogRel(x) ++#else ++#define LogRelDebug(x) ++#endif ++ ++ ++//TODO: Add Cs, Ds, Es, Fs, Gs, SS, ... ++void VMR3UpdateFdpCpuCtx(PVMCPU pVCpu) ++{ ++ PCCPUMCTXCORE pCtxCore = CPUMGetGuestCtxCore(pVCpu); ++ FDP_CPU_CTX* pFdpCpuCtx = (FDP_CPU_CTX *)pVCpu->mystate.s.pCpuShm; ++ ++ pFdpCpuCtx->rip = pCtxCore->rip; ++ pFdpCpuCtx->rax = pCtxCore->rax; ++ pFdpCpuCtx->rcx = pCtxCore->rcx; ++ pFdpCpuCtx->rdx = pCtxCore->rdx; ++ pFdpCpuCtx->rbx = pCtxCore->rbx; ++ pFdpCpuCtx->rsp = pCtxCore->rsp; ++ pFdpCpuCtx->rbp = pCtxCore->rbp; ++ pFdpCpuCtx->rsi = pCtxCore->rsi; ++ pFdpCpuCtx->rdi = pCtxCore->rdi; ++ pFdpCpuCtx->r8 = pCtxCore->r8; ++ pFdpCpuCtx->r9 = pCtxCore->r9; ++ pFdpCpuCtx->r10 = pCtxCore->r10; ++ pFdpCpuCtx->r11 = pCtxCore->r11; ++ pFdpCpuCtx->r12 = pCtxCore->r12; ++ pFdpCpuCtx->r13 = pCtxCore->r13; ++ pFdpCpuCtx->r14 = pCtxCore->r14; ++ pFdpCpuCtx->r15 = pCtxCore->r15; ++ pFdpCpuCtx->cr0 = CPUMGetGuestCR0(pVCpu); ++ pFdpCpuCtx->cr2 = CPUMGetGuestCR2(pVCpu); ++ pFdpCpuCtx->cr3 = CPUMGetGuestCR3(pVCpu); ++ pFdpCpuCtx->cr4 = CPUMGetGuestCR4(pVCpu); ++ ++} ++ ++HardwarePage_t* VMR3GetAllocatedHardwarePage(PUVM pUVM, uint64_t GCPhys) ++{ ++ //Look for a breakpoint already using a convient page ++ int BreakpointId = VMMGetBreakpointIdFromPage(pUVM->pVM, GCPhys, FDP_SOFTHBP); ++ if(BreakpointId >= 0 ++ && BreakpointId < MAX_BREAKPOINT_ID){ ++ //A breakpoint using a convient page exists ++ pUVM->pVM->bp.l[BreakpointId].breakpointHardwarePage->ReferenceCount++; ++ return pUVM->pVM->bp.l[BreakpointId].breakpointHardwarePage; ++ } ++ ++ //Look in the Free HardwarePage table ++ for(uint32_t i=0; ipVM->mystate.s.u32HardwarePageTableCount; i++){ ++ if(pUVM->pVM->mystate.s.aHardwarePageTable[i].ReferenceCount == 0 ++ && pUVM->pVM->mystate.s.aHardwarePageTable[i].HCPhys != 0 ++ && pUVM->pVM->mystate.s.aHardwarePageTable[i].R3Ptr != NULL){ ++ pUVM->pVM->mystate.s.aHardwarePageTable[i].ReferenceCount = 1; ++ return &pUVM->pVM->mystate.s.aHardwarePageTable[i]; ++ } ++ } ++ ++ LogRel(("[WDEBUG] Allocate a new HardwarePage for %p !\n", GCPhys)); ++ //None are free, allocate a new one ! ++ ALLOCPAGEREQ Req; ++ Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; ++ Req.Hdr.cbReq = sizeof(Req); ++ Req.newPageSize = _4K; ++ int rc = SUPR3CallVMMR0Ex(pUVM->pVM->pVMR0, NIL_VMCPUID, VMMR0_DO_ALLOC_HCPHYS, 0, &Req.Hdr); ++ if(rc != 0){ ++ LogRel(("[WDEBUG] Failed to allocate a new HardwarePage %d\n", rc)); ++ return NULL; ++ } ++ ++ HardwarePage_t* TmpHardwarePage = &pUVM->pVM->mystate.s.aHardwarePageTable[pUVM->pVM->mystate.s.u32HardwarePageTableCount]; ++ ++ TmpHardwarePage->PageSize = _4K; ++ TmpHardwarePage->HCPhys = Req.newPageHCPHys; ++ TmpHardwarePage->R3Ptr = Req.newPageR3Ptr; ++ TmpHardwarePage->ReferenceCount = 1; ++ ++ pUVM->pVM->mystate.s.u32HardwarePageTableCount++; ++ ++ return TmpHardwarePage; ++} ++ ++ ++/* ++ * @brief: Restore all Original HCPhys for SoftHyperBreakpointed GCPhys ++ */ ++VMMR3_INT_DECL(int) VMR3RestoreAllOriginalPage(PUVM pUVM, bool bIsRead, bool bIsWrite, bool bIsExecute) ++{ ++ PVM pVM = pUVM->pVM; ++ PVMCPU pVCpu = &pVM->aCpus[0]; ++ BreakpointEntrie_t *pTempBreakpointEntrie = NULL; ++ for(uint8_t BreakpointId=4*pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ //Check if Breakpoint is Activated and if it is a SoftWareBreakpoint ++ if(pTempBreakpointEntrie->breakpointActivated == true && ++ pTempBreakpointEntrie->breakpointType == FDP_SOFTHBP && ++ pTempBreakpointEntrie->breakpointOrigHCPhys != 0x0 && ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable[0].Start != 0x0){ ++ //Set Original Page as Read and Write ++ uint64_t GCPhys = pTempBreakpointEntrie->breakpointGCPhysAreaTable[0].Start; ++ PGMShwSetHCPage(pVCpu, GCPhys, pTempBreakpointEntrie->breakpointOrigHCPhys); ++ if(bIsRead == true){ ++ PGMShwPresent(pVCpu, GCPhys); ++ }else{ ++ PGMShwNoPresent(pVCpu, GCPhys); ++ } ++ if(bIsWrite == true){ ++ PGMShwWrite(pVCpu, GCPhys); ++ }else{ ++ PGMShwNoWrite(pVCpu, GCPhys); ++ } ++ if(bIsExecute == true){ ++ PGMShwExecute(pVCpu, GCPhys); ++ }else{ ++ PGMShwNoExecute(pVCpu, GCPhys); ++ } ++ //Set page as breakable ! ++ PGMShwSetBreakable(pVCpu, GCPhys, true); ++ //Invalidate the page ! ++ PGMShwInvalidate(pVCpu, GCPhys); ++ } ++ } ++ return 0; ++} ++ ++VMMR3_INT_DECL(int) VMR3AddMsrBreakpoint(PUVM pUVM, uint8_t BreakpointAccessType, uint64_t BreakpointAddress) ++{ ++ PVM pVM = pUVM->pVM; ++ //Look for a free breakpoint ++ for(int BreakpointId=4*pUVM->pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == false){ ++ pTempBreakpointEntrie->breakpointActivated = true; ++ pTempBreakpointEntrie->breakpointGCPtr = BreakpointAddress; ++ pTempBreakpointEntrie->breakpointOrigHCPhys = 0x0; ++ pTempBreakpointEntrie->breakpointType = FDP_MSRHBP; ++ pTempBreakpointEntrie->breakpointLength = 1; ++ pTempBreakpointEntrie->breakpointAccessType = BreakpointAccessType; ++ pTempBreakpointEntrie->breakpointPageSize = 0x0; ++ pTempBreakpointEntrie->breakpointHardwarePage = NULL; ++ pTempBreakpointEntrie->breakpointGCPhysAreaCount = 0; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = NULL; ++ ++ return BreakpointId; ++ } ++ } ++ return -1; ++} ++ ++VMMR3_INT_DECL(int) VMR3AddCrBreakpoint(PUVM pUVM, uint8_t BreakpointAccessType, uint64_t BreakpointAddress) ++{ ++ PVM pVM = pUVM->pVM; ++ //Look for a free breakpoint ++ for(int BreakpointId=4*pUVM->pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == false){ ++ pTempBreakpointEntrie->breakpointActivated = true; ++ pTempBreakpointEntrie->breakpointGCPtr = BreakpointAddress; ++ pTempBreakpointEntrie->breakpointOrigHCPhys = 0x0; ++ pTempBreakpointEntrie->breakpointType = FDP_CRHBP; ++ pTempBreakpointEntrie->breakpointLength = 1; ++ pTempBreakpointEntrie->breakpointAccessType = BreakpointAccessType; ++ pTempBreakpointEntrie->breakpointPageSize = 0x0; ++ pTempBreakpointEntrie->breakpointHardwarePage = NULL; ++ pTempBreakpointEntrie->breakpointGCPhysAreaCount = 0; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = NULL; ++ ++ return BreakpointId; ++ } ++ } ++ return -1; ++} ++ ++VMMR3_INT_DECL(int) VMR3AddSoftBreakpoint(PUVM pUVM, PVMCPU pVCpu, uint8_t BreakpointAddressType, uint64_t BreakpointAddress, uint64_t BreakpointCr3) ++{ //TODO: Move it to VMMAll ! ++ ++ LogRel(("[WDEBUG] VMR3AddSoftBreakpoint in\n")); ++ VMR3RestoreAllOriginalPage(pUVM, true, true, false); ++ ++ PVM pVM = pUVM->pVM; ++ ++ //Convert GCPtr to GCPhys if needed ++ uint64_t GCPhys; ++ uint64_t GCPtr = 0; ++ if(BreakpointAddressType == 0x1){//Virtual ++ GCPtr = BreakpointAddress; ++ PGMPhysGCPtr2GCPhys(pVCpu, BreakpointAddress, &GCPhys); ++ //PGMGstGetPage(pVCpu, BreakpointAddress, NULL, &GCPhys); ++ }else{ //Physical ++ GCPhys = BreakpointAddress; ++ } ++ ++ //Look for an already existing SoftHyperBreakpoint with same GCPhys ++ for(int BreakpointId=4*pUVM->pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true && ++ pTempBreakpointEntrie->breakpointType == FDP_SOFTHBP && ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable[0].Start == GCPhys){ ++ //We found one ! ++ return BreakpointId; ++ } ++ } ++ ++ for(int BreakpointId=4*pUVM->pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ ++ //Find a free breakpoint ++ if(pTempBreakpointEntrie->breakpointActivated == false){ ++ //Get Original HCPhys ++ uint64_t origHCPhys; ++ //It is the OriginalPage because we called VMR3RestoreAllOriginalPage(pUVM); ++ PGMShwGetHCPage(pVCpu, GCPhys, &origHCPhys); ++ //After restore EPTPTE are not initilized ++ if(origHCPhys < 0x100){ ++ LogRel(("[WDEBUG] AddSoft %p %p\n", GCPhys, origHCPhys)); ++ return -1; ++ } ++ ++ //Get a HardwarePage ++ HardwarePage_t* pTempHardwarePage = VMR3GetAllocatedHardwarePage(pUVM, GCPhys); ++ if(pTempHardwarePage != NULL){ ++ if(pTempHardwarePage->ReferenceCount == 1){ ++ //This is the first breakpoint using this hardware page ++ //Copy original page content to new page only if the page is new ++ LogRel(("[WDEBUG] Copy OrignalPage to ModificatedPage\n")); ++ PGMPhysSimpleReadGCPhys(pUVM->pVM, (void*)pTempHardwarePage->R3Ptr, (RTGCPHYS)(GCPhys & ~(pTempHardwarePage->PageSize-1)), pTempHardwarePage->PageSize); ++ } ++ ++ LogRel(("[WDEBUG] SoftHyperBreakpoint installation : \n")); ++ LogRel(("[WDEBUG] Original page HCPhys: 0x%p\n", origHCPhys)); ++ LogRel(("[WDEBUG] GCPhys: 0x%p\n", GCPhys)); ++ LogRel(("[WDEBUG] pTempHardwarePage: %p\n", pTempHardwarePage)); ++ LogRel(("[WDEBUG] Modificated page HCPhys: 0x%p\n", pTempHardwarePage->HCPhys)); ++ LogRel(("[WDEBUG] Modificated page R3Ptr: 0x%p\n", pTempHardwarePage->R3Ptr)); ++ LogRel(("[WDEBUG] HardwarePage Reference Count: %d\n", pTempHardwarePage->ReferenceCount)); ++ LogRel(("[WDEBUG] \n")); ++ ++ pTempBreakpointEntrie->breakpointActivated = true; ++ pTempBreakpointEntrie->breakpointGCPtr = GCPtr; ++ pTempBreakpointEntrie->breakpointOrigHCPhys = origHCPhys; ++ pTempBreakpointEntrie->breakpointType = FDP_SOFTHBP; ++ pTempBreakpointEntrie->breakpointLength = 1; ++ pTempBreakpointEntrie->breakpointCr3 = BreakpointCr3; ++ pTempBreakpointEntrie->breakpointAccessType = FDP_EXECUTE_BP; ++ pTempBreakpointEntrie->breakpointPageSize = pTempHardwarePage->PageSize; ++ pTempBreakpointEntrie->breakpointHardwarePage = pTempHardwarePage; ++ //Save the original byte ++ pTempBreakpointEntrie->breakpointOriginalByte = pTempHardwarePage->R3Ptr[(GCPhys & (pTempHardwarePage->PageSize-1))]; //TODO change % to & ++ //Install a HLT in the new page ++ pTempHardwarePage->R3Ptr[(GCPhys & (pTempHardwarePage->PageSize-1))] = 0xCC; ++ pTempBreakpointEntrie->breakpointGCPhysAreaCount = 1; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = (GCPhysArea_t*)malloc(1 * sizeof(GCPhysArea_t)); ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable[0].Start = GCPhys; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable[0].End = GCPhys+1; ++ ++ //Set page as original and read/write ++ PGMShwSetHCPage(pVCpu, GCPhys, origHCPhys); ++ PGMShwPresent(pVCpu, GCPhys); ++ PGMShwWrite(pVCpu, GCPhys); ++ PGMShwNoExecute(pVCpu, GCPhys); ++ //Set page as breakable ! ++ PGMShwSetBreakable(pVCpu, GCPhys, true); ++ //Invalidate the page ! ++ PGMShwInvalidate(pVCpu, GCPhys); ++ ++ return BreakpointId; ++ }else{ ++ LogRel(("[WDEBUG] Failed to Allocate HardwarePage\n")); ++ return -1; ++ } ++ } ++ } ++ return -1; ++} ++ ++void ApplyBreakpointOnPage(PVM pVM, PVMCPU pVCpu, uint64_t GCPhys, uint8_t BreakpointAccessType) ++{ ++ //No access at all for FDP_READ_BP ++ if(BreakpointAccessType & FDP_READ_BP){ ++ PGMShwNoPresent(pVCpu, GCPhys); ++ PGMShwNoWrite(pVCpu, GCPhys); ++ PGMShwNoExecute(pVCpu, GCPhys); ++ } ++ if(BreakpointAccessType & FDP_WRITE_BP) ++ PGMShwNoWrite(pVCpu, GCPhys); ++ if(BreakpointAccessType & FDP_EXECUTE_BP) ++ PGMShwNoExecute(pVCpu, GCPhys); ++ ++ //Set the page as Breakable page ++ PGMShwSetBreakable(pVCpu, GCPhys, true); ++ //Save the final page rights ++ PGMShwSaveRights(pVCpu, GCPhys); ++ //Invalidate the page ! ++ PGMShwInvalidate(pVCpu, GCPhys); ++} ++ ++void DisableBreakpointOnPage(PVM pVM, PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ PGMShwPresent(pVCpu, GCPhys); ++ PGMShwWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); ++ ++ //Set the page as Standard page ++ PGMShwSetBreakable(pVCpu, GCPhys, false); ++ //Save the final page rights ++ PGMShwSaveRights(pVCpu, GCPhys); ++ //Invalidate the page ! ++ PGMShwInvalidate(pVCpu, GCPhys); ++} ++ ++#define MIN(a,b) (((a)<(b))?(a):(b)) ++ ++ ++void AddGCPhysAreaInBreakpoint(BreakpointEntrie_t *pTempBreakpointEntrie, uint64_t Start, uint64_t End) ++{ ++ if(pTempBreakpointEntrie){ ++ int CurrentGCPhysAreaIndex = pTempBreakpointEntrie->breakpointGCPhysAreaCount; ++ //LogRel(("[WDEBUG] %d. %p->%p\n", CurrentGCPhysAreaIndex, Start, End)); ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable[CurrentGCPhysAreaIndex].Start = Start; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable[CurrentGCPhysAreaIndex].End = End; ++ pTempBreakpointEntrie->breakpointGCPhysAreaCount++; ++ } ++} ++ ++void DisableAllPageBreakpoint(PVM pVM, PVMCPU pVCpu) ++{ ++ //Restore all rights for pages in breakpoint ++ for(int BreakpointId=0; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_PAGEHBP ++ && pTempBreakpointEntrie->breakpointGCPhysAreaTable){ ++ for(int j=0; jbreakpointGCPhysAreaCount; j++){ ++ DisableBreakpointOnPage(pVM, pVCpu, pTempBreakpointEntrie->breakpointGCPhysAreaTable[j].Start); ++ } ++ } ++ } ++} ++ ++void EnableAllPageBreakpoint(PVM pVM, PVMCPU pVCpu) ++{ ++ //Restore all rights for pages in breakpoint ++ for(int BreakpointId=0; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_PAGEHBP ++ && pTempBreakpointEntrie->breakpointGCPhysAreaTable){ ++ //Apply on pages ++ for(int j=0; jbreakpointGCPhysAreaCount; j++){ ++ ApplyBreakpointOnPage(pVM, pVCpu, pTempBreakpointEntrie->breakpointGCPhysAreaTable[j].Start, pTempBreakpointEntrie->breakpointAccessType); ++ } ++ } ++ } ++} ++ ++void InstallAllPageBreakpoint(PVM pVM, PVMCPU pVCpu) ++{ ++ //Restore all rights for pages in breakpoint ++ for(int BreakpointId=0; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_PAGEHBP ++ && pTempBreakpointEntrie->breakpointGCPhysAreaTable){ ++ for(int j=0; jbreakpointGCPhysAreaCount; j++){ ++ DisableBreakpointOnPage(pVM, pVCpu, pTempBreakpointEntrie->breakpointGCPhysAreaTable[j].Start); ++ } ++ if(pTempBreakpointEntrie->breakpointGCPhysAreaTable) ++ free(pTempBreakpointEntrie->breakpointGCPhysAreaTable); ++ pTempBreakpointEntrie->breakpointGCPhysAreaCount = 0; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = NULL; ++ } ++ } ++ ++ //Remove rights for pages in breakpoint ++ for(int BreakpointId=0; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_PAGEHBP){ ++ uint64_t BreakpointLength = pTempBreakpointEntrie->breakpointLength; ++ if(pTempBreakpointEntrie->breakpointGCPtr > 0){ //VirtualAddress Breakpoint ++ uint64_t GCPhys; ++ uint64_t GCPtr = pTempBreakpointEntrie->breakpointGCPtr; ++ int MaxGCPhysAreaCount = (BreakpointLength/_4K) + 1; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = (GCPhysArea_t*)malloc(MaxGCPhysAreaCount * sizeof(GCPhysArea_t)); ++ ++ //First chunk Page ++ int rc = PGMPhysGCPtr2GCPhys(pVCpu, GCPtr, &GCPhys); ++ uint64_t GCPhysPageEnd = (GCPhys & 0xFFFFFFFFFFFFF000) + _4K; ++ uint64_t AlreadyBreakpointSize = MIN(GCPhysPageEnd - GCPhys, BreakpointLength); ++ if(RT_SUCCESS(rc)){ ++ AddGCPhysAreaInBreakpoint(pTempBreakpointEntrie, GCPhys, GCPhys+AlreadyBreakpointSize); ++ } ++ //Intermediate complete page ++ int64_t LeftToBreakpoint = BreakpointLength - AlreadyBreakpointSize; ++ while (LeftToBreakpoint >= _4K){ //More than 1 page to breakpoint ! ++ rc = PGMPhysGCPtr2GCPhys(pVCpu, GCPtr + AlreadyBreakpointSize, &GCPhys); ++ if(RT_SUCCESS(rc)){ ++ AddGCPhysAreaInBreakpoint(pTempBreakpointEntrie, GCPhys, GCPhys+_4K); ++ } ++ LeftToBreakpoint = LeftToBreakpoint - _4K; ++ AlreadyBreakpointSize = AlreadyBreakpointSize + _4K; ++ } ++ ++ //Last chunk page ++ if (LeftToBreakpoint > 0){ //Left breakpoint bytes ++ rc = PGMPhysGCPtr2GCPhys(pVCpu, GCPtr + AlreadyBreakpointSize, &GCPhys); ++ if(RT_SUCCESS(rc)){ ++ AddGCPhysAreaInBreakpoint(pTempBreakpointEntrie, GCPhys, GCPhys+LeftToBreakpoint); ++ } ++ } ++ }else{ //PhysicalAddress Breakpoint ++ uint64_t LeftToBreakpoint = 0; ++ int MaxGCPhysAreaCount = (BreakpointLength/_4K) + 1; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = (GCPhysArea_t*)malloc(MaxGCPhysAreaCount * sizeof(GCPhysArea_t)); ++ uint64_t GCPhysPageEnd = (pTempBreakpointEntrie->breakpointGCPhys & ~(_4K-1)) + _4K; ++ uint64_t LastPageEnd = MIN(GCPhysPageEnd, pTempBreakpointEntrie->breakpointGCPhys+BreakpointLength); ++ LeftToBreakpoint = BreakpointLength - (LastPageEnd - pTempBreakpointEntrie->breakpointGCPhys); ++ AddGCPhysAreaInBreakpoint(pTempBreakpointEntrie, pTempBreakpointEntrie->breakpointGCPhys, LastPageEnd); ++ while(LeftToBreakpoint >= _4K){ ++ AddGCPhysAreaInBreakpoint(pTempBreakpointEntrie, LastPageEnd, LastPageEnd+_4K); ++ LeftToBreakpoint -= _4K; ++ LastPageEnd += _4K; ++ } ++ if(LeftToBreakpoint > 0){ ++ AddGCPhysAreaInBreakpoint(pTempBreakpointEntrie, LastPageEnd, LastPageEnd+LeftToBreakpoint); ++ } ++ } ++ ++ ++ //Apply on pages ++ for(int j=0; jbreakpointGCPhysAreaCount; j++){ ++ ApplyBreakpointOnPage(pVM, pVCpu, pTempBreakpointEntrie->breakpointGCPhysAreaTable[j].Start, pTempBreakpointEntrie->breakpointAccessType); ++ } ++ } ++ } ++ ++ return; ++} ++ ++ ++bool IsOneCPURunning(PUVM pUVM) ++{ ++ for(uint32_t i=0; ipVM->aCpus[i].mystate.s.u8StateBitmap & FDP_STATE_PAUSED)){ ++ return true; ++ } ++ } ++ return false; ++} ++ ++ ++ ++ ++VMMR3_INT_DECL(int) VMR3AddPageBreakpoint(PUVM pUVM, PVMCPU pVCpu, uint8_t BreakpointId, uint8_t BreakpointAccessType, uint8_t BreakpointAddressType, uint64_t BreakpointAddress, uint64_t BreakpointLength) ++{ //TODO: Move it to VMMAll ! ++ if(IsOneCPURunning(pUVM) == true){ ++ //NO WAY !!!!!! ++ return -1; ++ } ++ ++ PVM pVM = pUVM->pVM; ++ ++ BreakpointEntrie_t *pTempBreakpointEntrie = NULL; ++ //If not a reserved to the guest breakpoint ++ if(BreakpointId < 0 || BreakpointId > 3){ ++ bool BreakpointIdFound = false; ++ for(BreakpointId=4*pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ //Find a free breakpoint ++ if(pTempBreakpointEntrie->breakpointActivated == false){ ++ break; ++ } ++ } ++ }else{ ++ pTempBreakpointEntrie = &pVM->bp.l[BreakpointId]; ++ } ++ ++ if(pTempBreakpointEntrie != NULL ++ && pTempBreakpointEntrie->breakpointActivated == false){ ++ uint64_t GCPhys; ++ uint64_t GCPtr = 0; ++ if(BreakpointAddressType == FDP_VIRTUAL_ADDRESS){//Virtual ++ GCPtr = BreakpointAddress; ++ int rc = PGMPhysGCPtr2GCPhys(pVCpu, BreakpointAddress, &GCPhys); ++ if(RT_FAILURE(rc)){ ++ //LogRel(("Fail to convert GCPtr(%p) -> GCphys\n", BreakpointAddress)); ++ return -1; ++ } ++ }else{ //Physical ++ GCPhys = BreakpointAddress; ++ } ++ ++ pVM->bp.l[BreakpointId].breakpointActivated = true; ++ pVM->bp.l[BreakpointId].breakpointGCPtr = GCPtr; ++ pVM->bp.l[BreakpointId].breakpointGCPhys = GCPhys; ++ pVM->bp.l[BreakpointId].breakpointType = FDP_PAGEHBP; ++ pVM->bp.l[BreakpointId].breakpointLength = BreakpointLength; ++ pVM->bp.l[BreakpointId].breakpointAccessType = BreakpointAccessType; ++ pVM->bp.l[BreakpointId].breakpointPageSize = _4K; ++ ++ InstallAllPageBreakpoint(pVM, pVCpu); ++ return BreakpointId; ++ } ++ return -1; ++} ++ ++//TODO: Move it to VMMAll ! ++VMMR3_INT_DECL(bool) VMR3RemoveBreakpoint(PUVM pUVM, int BreakpointId) ++{ ++ ++ if(BreakpointId < 0 || BreakpointId > MAX_BREAKPOINT_ID){ ++ return false; ++ } ++ ++ //If one Cpu is running, we can't remove a breakpoint ! ++ if(IsOneCPURunning(pUVM) == true){ ++ return false; ++ } ++ ++ PVM pVM = pUVM->pVM; ++ PVMCPU pVCpu = &pVM->aCpus[0]; ++ ++ //Restore OriginalPage for all SoftHyperBreakpoint ++ VMR3RestoreAllOriginalPage(pVM->pUVM, true, true, false); ++ ++ BreakpointEntrie_t *pTempBreakpointEntrie = &pVM->bp.l[BreakpointId]; ++ ++ if(pTempBreakpointEntrie->breakpointActivated == true){ ++ //Set the breakpoint as disabled ++ pTempBreakpointEntrie->breakpointActivated = false; ++ ++ switch(pTempBreakpointEntrie->breakpointType) ++ { ++ case FDP_PAGEHBP: ++ { ++ //Enable all rights for page in this breakpoint ++ for(int j=0; jbreakpointGCPhysAreaCount; j++){ ++ DisableBreakpointOnPage(pVM, pVCpu, pTempBreakpointEntrie->breakpointGCPhysAreaTable[j].Start); ++ } ++ //Enable all other page Breakpoint ++ InstallAllPageBreakpoint(pVM, pVCpu); ++ break; ++ } ++ case FDP_SOFTHBP: ++ { ++ pTempBreakpointEntrie->breakpointHardwarePage->ReferenceCount--; ++ if(pTempBreakpointEntrie->breakpointHardwarePage->ReferenceCount == 0){ ++ uint64_t GCPhys = pTempBreakpointEntrie->breakpointGCPhysAreaTable[0].Start; ++ LogRelDebug(("[WDEBUG] HardwarePage->ReferenceCount == 0\n")); ++ //No more breakpoint use this HardwarePage ! ++ //Restore Original page ++ PGMShwSetHCPage(pVCpu, GCPhys, pTempBreakpointEntrie->breakpointOrigHCPhys); ++ PGMShwPresent(pVCpu, GCPhys); ++ PGMShwWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); ++ ++ PGMShwSetBreakable(pVCpu, GCPhys, false); ++ ++ PGMShwInvalidate(pVCpu, GCPhys); ++ } ++ break; ++ } ++ default: ++ break; ++ } ++ ++ pTempBreakpointEntrie->breakpointTag = 0; ++ pTempBreakpointEntrie->breakpointGCPtr = 0; ++ pTempBreakpointEntrie->breakpointType = 0; ++ pTempBreakpointEntrie->breakpointLength = 0; ++ pTempBreakpointEntrie->breakpointAccessType = 0x0; ++ pTempBreakpointEntrie->breakpointOrigHCPhys = 0x0; ++ pTempBreakpointEntrie->breakpointOriginalByte = 0x0; ++ pTempBreakpointEntrie->breakpointHardwarePage = NULL; ++ pTempBreakpointEntrie->breakpointPageSize = 0x0; ++ ++ if(pTempBreakpointEntrie->breakpointGCPhysAreaTable){ ++ free(pTempBreakpointEntrie->breakpointGCPhysAreaTable); ++ } ++ ++ pTempBreakpointEntrie->breakpointGCPhysAreaCount = 0; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = NULL; ++ return true; ++ } ++ return false; ++} ++ ++VMMDECL(int) VMR3PhysSimpleReadGCPhysU(PUVM pUVM, void *pvDst, RTGCPHYS GCPhysSrc, size_t cb) ++{ ++ return PGMPhysSimpleReadGCPhys(pUVM->pVM, pvDst, GCPhysSrc, cb); ++} ++ ++VMMDECL(int) VMR3PhysSimpleWriteGCPhysU(PUVM pUVM, const void *pvBuf, RTGCPHYS GCPhys, size_t cbWrite) ++{ ++ return PGMPhysSimpleWriteGCPhys(pUVM->pVM, GCPhys, pvBuf, cbWrite); ++} ++ ++VMMDECL(int) VMR3SingleStep(PUVM pUVM, PVMCPU pVCpu) ++{ ++ //Dont try to single step on a running ++ if(pVCpu->mystate.s.u8StateBitmap & FDP_STATE_PAUSED){ ++ pVCpu->mystate.s.bSingleStepRequired = true; ++ while(pVCpu->mystate.s.bSingleStepRequired){ ++ //Yield ++ RTThreadSleep(0); ++ } ++ return 0; ++ } ++ return -1; ++} ++ ++VMMDECL(int) VMR3BreakNoLock(PUVM pUVM) ++{ ++ //LogRel(("[WDEBUG] BREAK !\n")); ++ ++ //Wait for all cpu paused ++ for(uint32_t i=0; ipVM->aCpus[i].mystate.s.bPauseRequired = true; ++ ++ //Inject a IPI ++ SUPR3CallVMMR0Ex(pUVM->pVM->pVMR0, pUVM->pVM->aCpus[i].idCpu, VMMR0_DO_GVMM_SCHED_WAKE_UP, 0, NULL); ++ SUPR3CallVMMR0Ex(pUVM->pVM->pVMR0, pUVM->pVM->aCpus[i].idCpu, VMMR0_DO_GVMM_SCHED_POKE, 0, NULL); ++ ++ do{ ++ //LogRel(("[WDEBUG] Waiting for CPU[%d] to pause state %02x\n", i, pUVM->pVM->aCpus[i].mystate.s.u8StateBitmap)); ++ //RTThreadSleep(10); ++ }while(!(pUVM->pVM->aCpus[i].mystate.s.u8StateBitmap & FDP_STATE_PAUSED)); ++ //LogRel(("[WDEBUG] CPU[%d] is paused !\n", i)); ++ } ++ ++ //LogRel(("[WDEBUG] All Cpus are PAUSED !\n")); ++ return 0; ++} ++ ++VMMDECL(int) VMR3Break(PUVM pUVM) ++{ ++ RTSpinlockAcquire(pUVM->pVM->mystate.s.CpuLock); ++ ++ VMR3BreakNoLock(pUVM); ++ ++ RTSpinlockRelease(pUVM->pVM->mystate.s.CpuLock); ++ return 0; ++} ++ ++VMMDECL(int) VMR3ContinueNoWaitNoLock(PUVM pUVM) ++{ ++ //LogRel(("[WDEBUG] VMR3ContinueNoWaitNoLock !\n")); ++ ++ pUVM->pVM->mystate.s.u8StateBitmap &= ~FDP_STATE_DEBUGGER_ALERTED; ++ ++ //Wait for all CPUs resumed ++ for(uint32_t i=0; ipVM->aCpus[i]; ++ uint64_t oldu64TickCount = pVCpu->mystate.s.u64TickCount; ++ pVCpu->mystate.s.bPauseRequired = false; ++ VMCPU_FF_SET(pVCpu, VMCPU_FF_EXTERNAL_SUSPENDED_MASK); ++ } ++ ++ return 0; ++} ++ ++VMMDECL(int) VMR3ContinueWaitNoLock(PUVM pUVM) ++{ ++ //LogRel(("[WDEBUG] VMR3ContinueWaitNoLock !\n")); ++ ++ pUVM->pVM->mystate.s.u8StateBitmap &= ~FDP_STATE_DEBUGGER_ALERTED; ++ ++ //Wait for all CPUs resumed ++ for(uint32_t i=0; ipVM->aCpus[i]; ++ uint64_t oldu64TickCount = pVCpu->mystate.s.u64TickCount; ++ pVCpu->mystate.s.bPauseRequired = false; ++ VMCPU_FF_SET(pVCpu, VMCPU_FF_EXTERNAL_SUSPENDED_MASK); ++ while(oldu64TickCount == pVCpu->mystate.s.u64TickCount){ ++ //Yield ++ RTThreadSleep(0); ++ } ++ } ++ ++ return 0; ++} ++ ++VMMDECL(int) VMR3Continue(PUVM pUVM) ++{ ++ RTSpinlockAcquire(pUVM->pVM->mystate.s.CpuLock); ++ ++ VMR3ContinueWaitNoLock(pUVM); ++ ++ RTSpinlockRelease(pUVM->pVM->mystate.s.CpuLock); ++ return 0; ++} ++ ++VMMDECL(uint8_t) VMR3GetFDPState(PUVM pUVM) ++{ ++ uint8_t u8OldState = 0; ++ bool bIsPaused = true; ++ bool bIsBreakpointHitted = false; ++ for(uint32_t i=0; ipVM->aCpus[i].mystate.s.u8StateBitmap & FDP_STATE_PAUSED)){ ++ bIsPaused = false; ++ } ++ //If one CPU hit a breakpoint ++ if(pUVM->pVM->aCpus[i].mystate.s.u8StateBitmap & FDP_STATE_BREAKPOINT_HIT){ ++ bIsBreakpointHitted = true; ++ } ++ } ++ ++ if(bIsPaused){ ++ u8OldState |= FDP_STATE_PAUSED; ++ if(bIsBreakpointHitted){ ++ u8OldState |= FDP_STATE_BREAKPOINT_HIT; ++ } ++ } ++ ++ if(pUVM->pVM->mystate.s.u8StateBitmap & FDP_STATE_DEBUGGER_ALERTED){ ++ u8OldState |= FDP_STATE_DEBUGGER_ALERTED; ++ } ++ if(u8OldState & FDP_STATE_BREAKPOINT_HIT){ ++ pUVM->pVM->mystate.s.u8StateBitmap |= FDP_STATE_DEBUGGER_ALERTED; ++ } ++ return u8OldState; ++} ++ ++VMMDECL(bool) VMR3DisableAllMsrBreakpoint(PVM pVM) ++{ ++ for(int BreakpointId=4*pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_MSRHBP){ ++ pTempBreakpointEntrie->breakpointActivated = false; ++ } ++ } ++ return 0; ++} ++ ++VMMDECL(bool) VMR3EnableAllMsrBreakpoint(PVM pVM) ++{ ++ for(int BreakpointId=4*pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointType == FDP_MSRHBP){ ++ pTempBreakpointEntrie->breakpointActivated = true; ++ } ++ } ++ return 0; ++} ++ ++VMMDECL(bool) VMR3HandleSingleStep(PVM pVM, PVMCPU pVCpu) ++{ ++ //Check if Single step is required ! ++ if(pVCpu->mystate.s.bSingleStepRequired){ ++ ++ TMR3NotifyResume(pVM, pVCpu); ++ ++ LogRelDebug(("[WDEBUG] CPU[%d] bSingleStepRequired !\n", pVCpu->idCpu)); ++ ++ //Restore Original Page with Execute right, avoid Breakpoint in SingleStep ++ VMR3RestoreAllOriginalPage(pVM->pUVM, true, true, true); ++ //Disable All Msr Breakpoint, avoid Breakpoint in Breakpoint ++ VMR3DisableAllMsrBreakpoint(pVM); ++ //Disable PageHyperBreapoint ++ DisableAllPageBreakpoint(pVM, pVCpu); ++ //Disable Debug Register ++ uint64_t OldDr7 = CPUMGetGuestDR7(pVCpu); ++ CPUMSetGuestDR7(pVCpu, 0x400); ++ ++ int rc = 0; ++ //First call is for instruction that jump on self "jmp -2 (ebfe)" ++ rc = VBOXSTRICTRC_VAL(EMR3HmSingleInstruction(pVM, pVCpu, 0)); ++ ++ LogRelDebug(("[WDEBUG] CPU[%d] Single Step => %d!\n", pVCpu->idCpu, rc)); ++ ++ if(VM_FF_IS_PENDING(pVM, VM_FF_ALL_REM_MASK) ++ || VMCPU_FF_IS_PENDING(pVCpu, VMCPU_FF_ALL_REM_MASK)){ ++ EMR3ProcessForcedAction(pVM, pVCpu, rc); ++ } ++ ++ //If rc == 0 then it failed, we have to call SingleInstruction whith RIP_CHANGE ++ if(rc == 0){ ++ rc = VBOXSTRICTRC_VAL(EMR3HmSingleInstruction(pVM, pVCpu, EM_ONE_INS_FLAGS_RIP_CHANGE)); ++ ++ LogRelDebug(("[WDEBUG] CPU[%d] Single Step => %d!\n", pVCpu->idCpu, rc)); ++ ++ if(VM_FF_IS_PENDING(pVM, VM_FF_ALL_REM_MASK) ++ || VMCPU_FF_IS_PENDING(pVCpu, VMCPU_FF_ALL_REM_MASK)){ ++ EMR3ProcessForcedAction(pVM, pVCpu, rc); ++ } ++ } ++ ++ //Restore Original Page, avoid VirtualBox being crazy with unknown HCPhys on SoftHyperBreakpoint ++ VMR3RestoreAllOriginalPage(pVM->pUVM, true, true, false); ++ //Enable All Msr Breakpoint ++ VMR3EnableAllMsrBreakpoint(pVM); ++ //Enable All PageHyperBreakpoint ++ EnableAllPageBreakpoint(pVM, pVCpu); ++ //Enable Debug Register ++ CPUMSetGuestDR7(pVCpu, OldDr7); ++ ++ TMR3NotifySuspend(pVM, pVCpu); ++ ++ pVCpu->mystate.s.bSingleStepRequired = false; //Single step no more required ! ++ return true; ++ } ++ return false; ++} ++ ++VMMDECL(int) VMR3InjectInterrupt(PVM pVM, PVMCPU pVCpu, uint32_t enmXcpt, uint32_t uErr, uint64_t Cr2) ++{ ++ return TRPMRaiseXcptErrCR2(pVCpu, NULL, (X86XCPT)enmXcpt, uErr, Cr2); ++} ++ ++#include ++ ++VMMDECL(int) VMR3ClearInterrupt(PUVM pUVM, PVMCPU pVCpu) ++{ ++ //return PDMR3UsbHasHub(pUVM); ++ PDMR3PowerOn(pUVM->pVM); ++ return 0; ++} ++ ++VMMDECL(void) VMR3SetFDPShm(PUVM pUVM, void *pFdpShm) ++{ ++ pUVM->pVM->mystate.s.pFdpShm = pFdpShm; ++} ++ ++VMMDECL(bool) VMR3EnterPause(PVM pVM, PVMCPU pVCpu) ++{ ++ if(pVCpu->idCpu == 0){ ++ //Update FDP_CPU_CTX ++ VMR3UpdateFdpCpuCtx(pVCpu); ++ TMR3NotifySuspend(pVM, pVCpu); ++ ++ //Active wait ++ uint32_t u32WaitCount = 0; ++ while(pVCpu->mystate.s.bPauseRequired == true){ ++ if(VMR3HandleSingleStep(pVM, pVCpu) == true){ ++ //Update FDP_CPU_CTX ++ VMR3UpdateFdpCpuCtx(pVCpu); ++ u32WaitCount = 0; ++ } ++ //Powersaving :) ++ if((u32WaitCount & 0xFFFFFF) == 0xFFFFFF){ ++ RTThreadSleep(5); ++ }else{ ++ u32WaitCount++; ++ } ++ } ++ ++ TMR3NotifyResume(pVM, pVCpu); ++ ++ //ProcessForcedAction avoid freeze in CLI...BP...SAVE...STI ++ if(VM_FF_IS_PENDING(pVM, VM_FF_ALL_REM_MASK) ++ || VMCPU_FF_IS_PENDING(pVCpu, VMCPU_FF_ALL_REM_MASK)){ ++ EMR3ProcessForcedAction(pVM, pVCpu, 0); ++ } ++ ++ ++ //Update FDP_CPU_CTX ++ VMR3UpdateFdpCpuCtx(pVCpu); ++ } ++ return true; ++} ++ ++#include "VMMInternal.h" ++ ++ ++#define DR0_ENABLED 0x3 ++#define DR1_ENABLED 0xC ++#define DR2_ENABLED 0x30 ++#define DR3_ENABLED 0xC0 ++ ++#define DR_READ 0x03 ++#define DR_WRITE 0x01 ++#define DR_EXECUTE 0x00 ++ ++/* ++* @brief Convert a Debug Register Breakpoint Type to FDP Breakpoint Type ++* ++*/ ++int GetDrType(uint64_t DrType) ++{ ++ switch(DrType){ ++ case DR_READ: ++ return FDP_READ_BP; ++ case DR_WRITE: ++ return FDP_WRITE_BP; ++ case DR_EXECUTE: ++ return FDP_EXECUTE_BP; ++ } ++ return 0; ++} ++ ++/* ++ * @brief Get the Breakpoint Lenght from Debug Register Breakpoint Lenght ++ */ ++uint64_t GetDrLength(uint64_t DrLength) ++{ ++ switch(DrLength){ ++ case 0: ++ return 1; ++ case 1: ++ return 2; ++ case 2: ++ return 8; ++ case 3: ++ return 4; ++ } ++ return 1; ++} ++ ++VMMR3DECL(uint32_t) VMR3GetCPUCount(PUVM pUVM) ++{ ++ return pUVM->pVM->cCpus; ++} ++ + + /** + * Halted VM Wait. +@@ -1085,6 +2037,122 @@ VMMR3_INT_DECL(void) VMR3NotifyCpuFFU(PUVMCPU pUVCpu, uint32_t fFlags) + */ + VMMR3_INT_DECL(int) VMR3WaitHalted(PVM pVM, PVMCPU pVCpu, bool fIgnoreInterrupts) + { ++ /*MYCODE*/ ++ LogRel(("[WDEBUG] VMR3WaitHalted\n")); ++ if(pVCpu->mystate.s.bInstallDrBreakpointRequired){ ++ LogRelDebug(("[WDEBUG] CPU[%d] Entering bInstallDrBreakpointRequired !!\n", pVCpu->idCpu)); ++ pVCpu->mystate.s.u8StateBitmap |= FDP_STATE_PAUSED; ++ ++ //Break all CPUs ++ VMR3Break(pVM->pUVM); ++ ++ //Remove all breakpoint ++ int BreakpointId = 0; ++ for(int BreakpointId = (0+(pVCpu->idCpu*4)); BreakpointId<(int)(4+(pVCpu->idCpu*4)); BreakpointId++){ ++ VMR3RemoveBreakpoint(pVM->pUVM, BreakpointId); ++ } ++ ++ //Install all breakpoint ++ LogRelDebug(("[WDEBUG] CPU[%d] aGuestDr[0] %p\n", pVCpu->idCpu, pVCpu->mystate.s.aGuestDr[0])); ++ LogRelDebug(("[WDEBUG] CPU[%d] aGuestDr[1] %p\n", pVCpu->idCpu, pVCpu->mystate.s.aGuestDr[1])); ++ LogRelDebug(("[WDEBUG] CPU[%d] aGuestDr[2] %p\n", pVCpu->idCpu, pVCpu->mystate.s.aGuestDr[2])); ++ LogRelDebug(("[WDEBUG] CPU[%d] aGuestDr[3] %p\n", pVCpu->idCpu, pVCpu->mystate.s.aGuestDr[3])); ++ LogRelDebug(("[WDEBUG] CPU[%d] aGuestDr[7] %p\n", pVCpu->idCpu, pVCpu->mystate.s.aGuestDr[7])); ++ ++ //Update Guest Breakpoint ++ for(uint8_t i=0; i<4; i++){ ++ if(pVCpu->mystate.s.aGuestDr[7] & (0x3<< (i*2))){ ++ uint8_t TEMP_DRX_LENGTH = (pVCpu->mystate.s.aGuestDr[7] & (0x3 << (18+i*4))) >> (18+i*4); ++ uint8_t DRX_LENGTH = GetDrLength(TEMP_DRX_LENGTH); ++ uint8_t TEMP_DRX_TYPE = (pVCpu->mystate.s.aGuestDr[7] & (0x3 << (16+i*4))) >> (16+i*4); ++ int DRX_TYPE = GetDrType(TEMP_DRX_TYPE); ++ ++ int BreakpointId = -1; ++ if(DRX_TYPE > 0){ ++ BreakpointId = VMR3AddPageBreakpoint(pVM->pUVM, pVCpu, i+(pVCpu->idCpu*4), DRX_TYPE, FDP_VIRTUAL_ADDRESS, pVCpu->mystate.s.aGuestDr[i], DRX_LENGTH); ++ } ++ //LogRel(("INSTALL DR[%d] %d\n", i, BreakpointId)); ++ } ++ } ++ ++ ++ pVCpu->mystate.s.u8StateBitmap &= ~FDP_STATE_PAUSED; ++ pVCpu->mystate.s.bPauseRequired = false; ++ pVCpu->mystate.s.bInstallDrBreakpointRequired = false; ++ ++ //Continue all CPUs ++ VMR3ContinueNoWaitNoLock(pVM->pUVM); ++ ++ LogRelDebug(("[WDEBUG] CPU[%d] Leaving bInstallDrBreakpointRequired !!\n", pVCpu->idCpu)); ++ return VINF_EM_RESCHEDULE; ++ } ++ ++ if(pVCpu->mystate.s.bHardHyperBreakPointHitted ++ || pVCpu->mystate.s.bPageHyperBreakPointHitted ++ || pVCpu->mystate.s.bSoftHyperBreakPointHitted ++ || pVCpu->mystate.s.bMsrHyperBreakPointHitted ++ || pVCpu->mystate.s.bCrHyperBreakPointHitted){ ++ ++ //Update FDP_CPU_CTX ++ VMR3UpdateFdpCpuCtx(pVCpu); ++ ++ if(pVCpu->mystate.s.bPageHyperBreakPointHitted){ ++ LogRel(("[WDEBUG] CPU[%d] bPageHyperBreakPointHitted !!\n", pVCpu->idCpu)); ++ } ++ if(pVCpu->mystate.s.bSoftHyperBreakPointHitted){ ++ LogRel(("[WDEBUG] CPU[%d] bSoftHyperBreakPointHitted !!\n", pVCpu->idCpu)); ++ } ++ if(pVCpu->mystate.s.bHardHyperBreakPointHitted){ ++ LogRel(("[WDEBUG] CPU[%d] bHardHyperBreakPointHitted !!\n", pVCpu->idCpu)); ++ pVCpu->mystate.s.u8StateBitmap |= FDP_STATE_HARD_BREAKPOINT_HIT; ++ } ++ if(pVCpu->mystate.s.bMsrHyperBreakPointHitted){ ++ LogRel(("[WDEBUG] CPU[%d] bMsrHyperBreakPointHitted !!\n", pVCpu->idCpu)); ++ } ++ if(pVCpu->mystate.s.bCrHyperBreakPointHitted){ ++ LogRel(("[WDEBUG] CPU[%d] bCrHyperBreakPointHitted !!\n", pVCpu->idCpu)); ++ } ++ ++ ++ //Restore OriginalPage for all SoftHyperBreakpoint ++ VMR3RestoreAllOriginalPage(pVM->pUVM, true, true, false); ++ ++ //Set the CPU as PAUSED and BREAKPOINT_HITTED ++ pVCpu->mystate.s.u8StateBitmap |= FDP_STATE_BREAKPOINT_HIT; ++ pVCpu->mystate.s.u8StateBitmap |= FDP_STATE_PAUSED; ++ ++ //Break all CPUs ++ VMR3Break(pVM->pUVM); ++ ++ //TODO: Protect this ! ++ FDP_SHM *pFdpShm = (FDP_SHM *)pVM->mystate.s.pFdpShm; ++ FDP_SetStateChanged(pFdpShm); ++ ++ //Waiting for debugger resume ! ++ VMR3EnterPause(pVM, pVCpu); ++ ++ bool bMsrHyperBreakpointHitted = pVCpu->mystate.s.bMsrHyperBreakPointHitted; ++ ++ //We are ready to go ! ++ pVCpu->mystate.s.bHardHyperBreakPointHitted = false; ++ pVCpu->mystate.s.bPageHyperBreakPointHitted = false; ++ pVCpu->mystate.s.bSoftHyperBreakPointHitted = false; ++ pVCpu->mystate.s.bMsrHyperBreakPointHitted = false; ++ pVCpu->mystate.s.bCrHyperBreakPointHitted = false; ++ pVCpu->mystate.s.u8StateBitmap = 0; ++ ++ //Single step for MsrBreakpoint... Maybe this stuff should be done in Winbagility... ++ if(bMsrHyperBreakpointHitted == true){ ++ pVCpu->mystate.s.bSingleStepRequired = true; ++ VMR3HandleSingleStep(pVM, pVCpu); ++ pVCpu->mystate.s.bSingleStepRequired = false; ++ } ++ ++ LogRel(("[WDEBUG] CPU[%d] Leaving Breakpoint !\n", pVCpu->idCpu)); ++ return VINF_EM_RESCHEDULE; ++ } ++ /*ENDMYCODE*/ ++ + LogFlow(("VMR3WaitHalted: fIgnoreInterrupts=%d\n", fIgnoreInterrupts)); + + /* +diff --git a/src/VBox/VMM/VMMR3/VMM.cpp b/src/VBox/VMM/VMMR3/VMM.cpp +index 96354825..bfaddbe1 100644 +--- a/src/VBox/VMM/VMMR3/VMM.cpp ++++ b/src/VBox/VMM/VMMR3/VMM.cpp +@@ -1408,7 +1408,11 @@ VMMR3_INT_DECL(int) VMMR3HmRunGC(PVM pVM, PVMCPU pVCpu) + if (RT_LIKELY(rc == VINF_SUCCESS)) + rc = pVCpu->vmm.s.iLastGZRc; + #endif +- } while (rc == VINF_EM_RAW_INTERRUPT_HYPER); ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired == true) ++ break; ++ /*ENDMYCODE*/ ++ } while (rc == VINF_EM_RAW_INTERRUPT_HYPER); + + #if 0 /** @todo triggers too often */ + Assert(!VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_TO_R3)); +diff --git a/src/VBox/VMM/include/PGMInternal.h b/src/VBox/VMM/include/PGMInternal.h +index 0424c371..e410e5db 100644 +--- a/src/VBox/VMM/include/PGMInternal.h ++++ b/src/VBox/VMM/include/PGMInternal.h +@@ -96,6 +96,9 @@ + #if (HC_ARCH_BITS == 64) && !defined(IN_RC) + # define PGM_WITH_LARGE_PAGES + #endif ++/*MYCODE*/ ++#undef PGM_WITH_LARGE_PAGES ++/*ENDMYENCODE*/ + + /** + * Enables optimizations for MMIO handlers that exploits X86_TRAP_PF_RSVD and +diff --git a/src/VBox/VMM/testcase/tstAsmStructs.cpp b/src/VBox/VMM/testcase/tstAsmStructs.cpp +index f940e11d..df798514 100644 +--- a/src/VBox/VMM/testcase/tstAsmStructs.cpp ++++ b/src/VBox/VMM/testcase/tstAsmStructs.cpp +@@ -53,5 +53,6 @@ int main() + printf("tstAsmStructs: FAILURE - %d errors \n", rc); + else + printf("tstAsmStructs: SUCCESS\n"); ++// printf("tstAsmStructs: SKIPPING\n"); + return rc; + } diff --git a/FDPutils/VirtualBox-6.0.8_FDP_macOS.patch b/FDPutils/VirtualBox-6.0.8_FDP_macOS.patch new file mode 100755 index 00000000..968acfa8 --- /dev/null +++ b/FDPutils/VirtualBox-6.0.8_FDP_macOS.patch @@ -0,0 +1,3864 @@ +diff --git a/Config.kmk b/Config.kmk +index 65a61dba..17f14c90 100644 +--- a/Config.kmk ++++ b/Config.kmk +@@ -922,7 +922,7 @@ endif + # Continue to support Vista w/o any service pack, at least for now. + VBOX_WITH_VISTA_NO_SP = 1 + # Enable image verification on darwin @bugref{9232}. +-VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION = 1 ++#VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION = 0 + ## @} + + +@@ -1671,9 +1671,9 @@ ifdef VBOX_WITH_RAW_MODE + endif + + # Don't flood CDEFS, old MASMs doesn't like too many defines. +-ifdef VBOX_WITH_DEBUGGER ++#ifdef VBOX_WITH_DEBUGGER + CDEFS += VBOX_WITH_DEBUGGER +-endif ++#endif + ifdef VBOX_WITH_HARDENING + CDEFS += VBOX_WITH_HARDENING + endif +diff --git a/configure b/configure +index 886eabb4..8486fb40 100755 +--- a/configure ++++ b/configure +@@ -153,7 +153,8 @@ INCVPX="" + LIBVPX="-lvpx" + PKGCONFIG="`which_wrapper pkg-config`" + PYTHONDIR="/usr /usr/local" +-QT5DIR="/usr/lib/qt5 /usr/share/qt5 /usr/lib64/qt5 /usr /usr/local" ++#QT5DIR="/usr/lib/qt5 /usr/share/qt5 /usr/lib64/qt5 /usr /usr/local" ++QT5DIR="/usr/lib/qt5 /usr/share/qt5 /usr/lib64/qt5 /usr /usr/local /opt/local/libexec/qt5" + QT5DIR_PKGCONFIG=1 + QT5MAJ=5 + QT5MIN=6 +@@ -1494,7 +1495,8 @@ EOF + # Now try the user provided directory and some of the standard directories. + QT_TRIES="$QT5DIR /System/Library /Library" + for t in $QT_TRIES; do +- if [ -f "$t/Frameworks/QtCore.framework/QtCore" ]; then ++ #if [ -f "$t/Frameworks/QtCore.framework/QtCore" ]; then ++ if [ -f "$t/lib/QtCore.framework/QtCore" ]; then + PATH_SDK_QT5="$t" + break + fi +@@ -1502,8 +1504,10 @@ EOF + # Add the necessary params for building the test application + if [ -n "$PATH_SDK_QT5" ]; then + foundqt5=1 +- INCQT5=-I$PATH_SDK_QT5/Frameworks/QtCore.framework/Headers +- LIBQT5=-F$PATH_SDK_QT5/Frameworks ++ #INCQT5=-I$PATH_SDK_QT5/Frameworks/QtCore.framework/Headers ++ #LIBQT5=-F$PATH_SDK_QT5/Frameworks ++ INCQT5=-I$PATH_SDK_QT5/lib/QtCore.framework/Headers ++ LIBQT5=-F$PATH_SDK_QT5/lib + FLGQT5="-framework QtCore" + else + log_failure "Qt5 framework not found (can be disabled using --disable-qt)" +@@ -1584,9 +1588,12 @@ EOF + if [ "$OS" = "darwin" ]; then + # Successful build & run the test application so add the necessary + # params to AutoConfig.kmk: +- cnf_append "PATH_SDK_QT5_INC" "$PATH_SDK_QT5/Frameworks" +- cnf_append "PATH_SDK_QT5_LIB" "$PATH_SDK_QT5/Frameworks" +- cnf_append "PATH_SDK_QT5" "$PATH_SDK_QT5/Frameworks" ++ #cnf_append "PATH_SDK_QT5_INC" "$PATH_SDK_QT5/Frameworks" ++ #cnf_append "PATH_SDK_QT5_LIB" "$PATH_SDK_QT5/Frameworks" ++ #cnf_append "PATH_SDK_QT5" "$PATH_SDK_QT5/Frameworks" ++ cnf_append "PATH_SDK_QT5_INC" "$PATH_SDK_QT5/inc" ++ cnf_append "PATH_SDK_QT5_LIB" "$PATH_SDK_QT5/lib" ++ cnf_append "PATH_SDK_QT5" "$PATH_SDK_QT5" + # Check for the moc tool in the Qt directory found & some standard + # directories. + for q in $PATH_SDK_QT5 /usr /Developer/Tools/Qt; do +@@ -2186,6 +2193,14 @@ check_darwinversion() + test_header "Darwin version" + darwin_ver=`uname -r` + case "$darwin_ver" in ++ 18\.*) ++ check_xcode_sdk_path "$WITH_XCODE_DIR" ++ [ $? -eq 1 ] || fail ++ darwin_ver="10.14" # Mojave ++ sdk=$WITH_XCODE_DIR/Developer/SDKs/MacOSX10.6.sdk ++ cnf_append "VBOX_WITH_MACOSX_COMPILERS_FROM_DEVEL" "1" ++ cnf_append "VBOX_PATH_MACOSX_DEVEL_ROOT" "$WITH_XCODE_DIR/Developer" ++ ;; + 17\.*) + check_xcode_sdk_path "$WITH_XCODE_DIR" + [ $? -eq 1 ] || fail +diff --git a/include/VBox/vmm/em.h b/include/VBox/vmm/em.h +index 58c8777c..1c61a7dc 100644 +--- a/include/VBox/vmm/em.h ++++ b/include/VBox/vmm/em.h +@@ -398,6 +398,10 @@ VMMR3_INT_DECL(int) EMR3CheckRawForcedActions(PVM pVM, PVMCPU pVCpu) + VMMR3_INT_DECL(int) EMR3NotifyResume(PVM pVM); + VMMR3_INT_DECL(int) EMR3NotifySuspend(PVM pVM); + VMMR3_INT_DECL(VBOXSTRICTRC) EMR3HmSingleInstruction(PVM pVM, PVMCPU pVCpu, uint32_t fFlags); ++/*MYCODE*/ ++VMMR3_INT_DECL(void) EMR3ResetU(PUVM pUVM); ++VMMR3_INT_DECL(int) EMR3ProcessForcedAction(PVM pVM, PVMCPU pVCpu, int rc); ++/*ENDMYCODE*/ + + /** @} */ + #endif /* IN_RING3 */ +diff --git a/include/VBox/vmm/gmm.h b/include/VBox/vmm/gmm.h +index 0b6ec900..6568ebf4 100644 +--- a/include/VBox/vmm/gmm.h ++++ b/include/VBox/vmm/gmm.h +@@ -547,6 +547,16 @@ typedef GMMMEMSTATSREQ *PGMMMEMSTATSREQ; + GMMR0DECL(int) GMMR0QueryHypervisorMemoryStatsReq(PGMMMEMSTATSREQ pReq); + GMMR0DECL(int) GMMR0QueryMemoryStatsReq(PGVM pGVM, PVM pVM, VMCPUID idCpu, PGMMMEMSTATSREQ pReq); + ++/*MYCODE*/ ++typedef struct ALLOCPAGEREQ ++{ ++ SUPVMMR0REQHDR Hdr; ++ uint64_t newPageSize; ++ uint64_t newPageHCPHys; ++ uint8_t* newPageR3Ptr; ++} ALLOCPAGEREQ; ++/*ENDMYCODE*/ ++ + /** + * Request buffer for GMMR0MapUnmapChunkReq / VMMR0_DO_GMM_MAP_UNMAP_CHUNK. + * @see GMMR0MapUnmapChunk +diff --git a/include/VBox/vmm/hm.h b/include/VBox/vmm/hm.h +index c152338a..f0a5b9ee 100644 +--- a/include/VBox/vmm/hm.h ++++ b/include/VBox/vmm/hm.h +@@ -174,6 +174,9 @@ VMM_INT_DECL(TRPMEVENT) HMSvmEventToTrpmEventType(PCSVMEVENT pSvmEvent, + * @{ */ + VMM_INT_DECL(int) HMFlushTlb(PVMCPU pVCpu); + VMM_INT_DECL(int) HMFlushTlbOnAllVCpus(PVM pVM); ++/*MYCODE*/ ++VMM_INT_DECL(int) HMFlushTLBOnAllVCpus2(PVM pVM); ++/*ENDMYCODE*/ + VMM_INT_DECL(int) HMInvalidatePageOnAllVCpus(PVM pVM, RTGCPTR GCVirt); + VMM_INT_DECL(int) HMInvalidatePhysPage(PVM pVM, RTGCPHYS GCPhys); + VMM_INT_DECL(bool) HMAreNestedPagingAndFullGuestExecEnabled(PVM pVM); +@@ -223,6 +226,9 @@ VMM_INT_DECL(int) HMHCMaybeMovTprSvmHypercall(PVMCPU pVCpu); + * @{ + */ + VMMR0_INT_DECL(int) HMR0Init(void); ++/*MYCODE*/ ++VMMR0_INT_DECL(int) HMR0FlushEPT(PVM pVM, PVMCPU pVCpu); ++/*ENDMYCODE*/ + VMMR0_INT_DECL(int) HMR0Term(void); + VMMR0_INT_DECL(int) HMR0InitVM(PVM pVM); + VMMR0_INT_DECL(int) HMR0TermVM(PVM pVM); +diff --git a/include/VBox/vmm/mm.h b/include/VBox/vmm/mm.h +index 26f63fad..0e815160 100644 +--- a/include/VBox/vmm/mm.h ++++ b/include/VBox/vmm/mm.h +@@ -231,6 +231,9 @@ VMMDECL(bool) MMHyperIsInsideArea(PVM pVM, RTGCPTR GCPtr); + + VMMDECL(RTHCPHYS) MMPage2Phys(PVM pVM, void *pvPage); + VMMDECL(void *) MMPagePhys2Page(PVM pVM, RTHCPHYS HCPhysPage); ++/*MYCODE*/ ++VMMDECL(void *) MMPagePhys2PageU(PUVM pUVM, RTHCPHYS HCPhysPage); ++/*ENDCODE*/ + VMMDECL(int) MMPagePhys2PageEx(PVM pVM, RTHCPHYS HCPhysPage, void **ppvPage); + VMMDECL(int) MMPagePhys2PageTry(PVM pVM, RTHCPHYS HCPhysPage, void **ppvPage); + +@@ -298,6 +301,9 @@ VMMR3DECL(int) MMR3HyperReadGCVirt(PVM pVM, void *pvDst, RTGCPTR GCPtr, siz + * @todo retire this group, elimintating or moving MMR3PhysGetRamSize to PGMPhys. + * @{ */ + VMMR3DECL(uint64_t) MMR3PhysGetRamSize(PVM pVM); ++/*MYCODE*/ ++VMMR3DECL(uint64_t) MMR3PhysGetRamSizeU(PUVM pUVM); ++/*ENDMYCODE*/ + VMMR3DECL(uint32_t) MMR3PhysGetRamSizeBelow4GB(PVM pVM); + VMMR3DECL(uint64_t) MMR3PhysGetRamSizeAbove4GB(PVM pVM); + VMMR3DECL(uint32_t) MMR3PhysGet4GBRamHoleSize(PVM pVM); +diff --git a/include/VBox/vmm/pgm.h b/include/VBox/vmm/pgm.h +index 63b9129b..58d21e9f 100644 +--- a/include/VBox/vmm/pgm.h ++++ b/include/VBox/vmm/pgm.h +@@ -436,6 +436,21 @@ VMMDECL(int) PGMShwMakePageNotPresent(PVMCPU pVCpu, RTGCPTR GCPtr, ui + /** The page is an MMIO2. */ + #define PGM_MK_PG_IS_MMIO2 RT_BIT(1) + /** @}*/ ++/*MYCODE*/ ++VMMDECL(int) PGMShwGetHCPage(PVMCPU pVCpu, uint64_t GCPhys, uint64_t *HCPhys); ++VMMDECL(int) PGMShwSetHCPage(PVMCPU pVCpu, uint64_t GCPhys, uint64_t HCPhys); ++VMMDECL(int) PGMShwSaveRights(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwRestoreRights(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwPresent(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwNoPresent(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwWrite(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwNoWrite(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwExecute(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwNoExecute(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwInvalidate(PVMCPU pVCpu, uint64_t GCPhys); ++VMMDECL(int) PGMShwSetBreakable(PVMCPU pVCpu, uint64_t GCPhys, bool Breakable); ++VMMDECL(bool) PGMShwIsBreakable(PVMCPU pVCpu, uint64_t GCPhys); ++/*ENDMYCODE*/ + VMMDECL(int) PGMGstGetPage(PVMCPU pVCpu, RTGCPTR GCPtr, uint64_t *pfFlags, PRTGCPHYS pGCPhys); + VMMDECL(bool) PGMGstIsPagePresent(PVMCPU pVCpu, RTGCPTR GCPtr); + VMMDECL(int) PGMGstSetPage(PVMCPU pVCpu, RTGCPTR GCPtr, size_t cb, uint64_t fFlags); +@@ -657,6 +672,9 @@ VMMDECL(VBOXSTRICTRC) PGMPhysReadGCPtr(PVMCPU pVCpu, void *pvDst, RTGCPTR GCPtrS + VMMDECL(VBOXSTRICTRC) PGMPhysWriteGCPtr(PVMCPU pVCpu, RTGCPTR GCPtrDst, const void *pvSrc, size_t cb, PGMACCESSORIGIN enmOrigin); + + VMMDECL(int) PGMPhysSimpleReadGCPhys(PVM pVM, void *pvDst, RTGCPHYS GCPhysSrc, size_t cb); ++/*MYCODE*/ ++VMMDECL(int) PGMPhysSimpleReadGCPhys2(PUVM pUVM, void *pvDst, RTGCPHYS GCPhysSrc, size_t cb); ++/*MYCODE*/ + VMMDECL(int) PGMPhysSimpleWriteGCPhys(PVM pVM, RTGCPHYS GCPhysDst, const void *pvSrc, size_t cb); + VMMDECL(int) PGMPhysSimpleReadGCPtr(PVMCPU pVCpu, void *pvDst, RTGCPTR GCPtrSrc, size_t cb); + VMMDECL(int) PGMPhysSimpleWriteGCPtr(PVMCPU pVCpu, RTGCPTR GCPtrDst, const void *pvSrc, size_t cb); +@@ -788,6 +806,9 @@ VMMR0_INT_DECL(int) PGMR0PhysSetupIoMmu(PGVM pGVM, PVM pVM); + VMMR0DECL(int) PGMR0SharedModuleCheck(PVM pVM, PGVM pGVM, VMCPUID idCpu, PGMMSHAREDMODULE pModule, PCRTGCPTR64 paRegionsGCPtrs); + VMMR0DECL(int) PGMR0Trap0eHandlerNestedPaging(PVM pVM, PVMCPU pVCpu, PGMMODE enmShwPagingMode, RTGCUINT uErr, PCPUMCTXCORE pRegFrame, RTGCPHYS pvFault); + VMMR0DECL(VBOXSTRICTRC) PGMR0Trap0eHandlerNPMisconfig(PVM pVM, PVMCPU pVCpu, PGMMODE enmShwPagingMode, PCPUMCTXCORE pRegFrame, RTGCPHYS GCPhysFault, uint32_t uErr); ++/*MYCODE*/ ++VMMR0DECL(VBOXSTRICTRC) PGMR0PhysSimpleReadGCPhys(PVM pVM, void *pvDst, RTGCPHYS GCPhysSrc, size_t cb); ++/*ENDMYCODE*/ + # ifdef VBOX_WITH_2X_4GB_ADDR_SPACE + VMMR0DECL(int) PGMR0DynMapInit(void); + VMMR0DECL(void) PGMR0DynMapTerm(void); +@@ -817,6 +838,9 @@ VMMR3_INT_DECL(void) PGMR3ResetNoMorePhysWritesFlag(PVM pVM); + VMMR3_INT_DECL(void) PGMR3MemSetup(PVM pVM, bool fReset); + VMMR3DECL(int) PGMR3Term(PVM pVM); + VMMR3DECL(int) PGMR3LockCall(PVM pVM); ++/*MYCODE*/ ++VMMR3DECL(int) PGMR3ChangeMode2(PUVM pUVM, PVMCPU pVCpu, PGMMODE enmGuestMode); ++/*ENDMYYCODE*/ + + VMMR3DECL(int) PGMR3PhysRegisterRam(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, const char *pszDesc); + VMMR3DECL(int) PGMR3PhysChangeMemBalloon(PVM pVM, bool fInflate, unsigned cPages, RTGCPHYS *paPhysPage); +@@ -927,6 +951,10 @@ VMMR3DECL(int) PGMR3PhysBulkGCPhys2CCPtrReadOnlyExternal(PVM pVM, uint32_t + VMMR3DECL(int) PGMR3PhysChunkMap(PVM pVM, uint32_t idChunk); + VMMR3DECL(void) PGMR3PhysChunkInvalidateTLB(PVM pVM); + VMMR3DECL(int) PGMR3PhysAllocateHandyPages(PVM pVM); ++/*MYCODE*/ ++VMMR3DECL(int) PGMR3PhysAllocateLargeHandyPage2(PVM pVM); ++VMMR3_INT_DECL(int) PGMR3DbgScanPhysicalU(PUVM pUVM, RTGCPHYS GCPhys, RTGCPHYS cbRange, RTGCPHYS GCPhysAlign, const uint8_t *pabNeedle, size_t cbNeedle, PRTGCPHYS pGCPhysHit); ++/*ENDMYCODE*/ + VMMR3DECL(int) PGMR3PhysAllocateLargeHandyPage(PVM pVM, RTGCPHYS GCPhys); + + VMMR3DECL(int) PGMR3CheckIntegrity(PVM pVM); +diff --git a/include/VBox/vmm/vm.h b/include/VBox/vmm/vm.h +index fc25cefa..57a6bd85 100644 +--- a/include/VBox/vmm/vm.h ++++ b/include/VBox/vmm/vm.h +@@ -50,6 +50,61 @@ + * @{ + */ + ++/*MYCODE*/ ++typedef struct HardwarePage_t{ ++ int ReferenceCount; //Number of breakpoint using this page ++ uint64_t PageSize; //Size of the page ++ uint64_t HCPhys; //Host-Context physical address of the page ++ uint8_t* R3Ptr; //Ring-3 virtual address of the page ++}HardwarePage_t; ++ ++typedef struct GCPhysArea_t{ ++ uint64_t Start; ++ uint64_t End; ++}GCPhysArea_t; ++ ++typedef struct PfnEntrie_t{ ++ uint8_t n; ++ struct{ ++ bool u1Present; ++ bool u1Write; ++ bool u1Execute; ++ bool u1Breakable; ++ }u; ++}PfnEntrie_t; ++ ++typedef struct BreakpointEntrie_t{ ++ //Is the breakpoint activated or free ++ bool breakpointActivated; ++ //Tag the breakpoint to know if the breakpoint changed... ++ uint64_t breakpointTag; ++ //Kind of breakpoint PAGEHBP, HARDHBP, SOFTHBP ++ uint8_t breakpointType; ++ //Guest virtual address of the breakpoint or start of the breakpoint ++ uint64_t breakpointGCPtr; ++ //Guest physical address of the breakpoint or start of the breakpoint ++ uint64_t breakpointGCPhys; ++ //Lengt of the breakpoint (PAGEHBP only) ++ uint64_t breakpointLength; ++ //EXECUTE_BP, READ_BP, WRITE_BP ++ uint8_t breakpointAccessType; ++ //Size of the page where the breakpoint is ++ uint64_t breakpointPageSize; ++ //Original host physical page ++ uint64_t breakpointOrigHCPhys; ++ //Original opcode byte ++ uint8_t breakpointOriginalByte; ++ //Pointer to HardwarePage ++ HardwarePage_t* breakpointHardwarePage; ++ uint64_t breakpointGCPhysAreaCount; ++ GCPhysArea_t* breakpointGCPhysAreaTable; ++ //Condition ++ uint64_t breakpointCr3; ++}BreakpointEntrie_t; ++ ++#define MAX_BREAKPOINT_ID 255 ++/*ENDMYCODE*/ ++ + /** + * The state of a Virtual CPU. + * +@@ -142,6 +197,34 @@ typedef struct VMCPU + uint8_t padding[18496]; /* multiple of 64 */ + } iem; + ++ /*MYCODE*/ ++ union ++ { ++ struct{ ++ volatile uint8_t u8StateBitmap; ++ volatile bool bSingleStepRequired; ++ volatile bool bPauseRequired; ++ volatile bool bDisableInterrupt; ++ volatile bool bRebootRequired; ++ volatile bool bSuspendRequired; ++ volatile bool bRestoreRequired; ++ volatile bool bPageFaultOverflowGuard; ++ ++ volatile bool bHardHyperBreakPointHitted; ++ volatile bool bPageHyperBreakPointHitted; ++ volatile bool bSoftHyperBreakPointHitted; ++ volatile bool bMsrHyperBreakPointHitted; ++ volatile bool bCrHyperBreakPointHitted; ++ volatile bool bInstallDrBreakpointRequired; ++ //Fake Debug registers to keep "legit-guest" values ++ uint64_t aGuestDr[8]; ++ volatile uint64_t u64TickCount; ++ void* pCpuShm; ++ }s; ++ uint8_t padding[4096]; /* multiple of 4096 */ ++ } mystate; ++ /*ENDMYCODE*/ ++ + /** @name Static per-cpu data. + * (Putting this after IEM, hoping that it's less frequently used than it.) + * @{ */ +@@ -1356,6 +1439,28 @@ typedef struct VM + uint8_t padding[1600]; /* multiple of 64 */ + } vmm; + ++ /*MYCODE*/ ++ union{ ++ BreakpointEntrie_t l[MAX_BREAKPOINT_ID+1]; ++ uint8_t padding[4096*32]; /* Must be page aligned ! */ ++ }bp; ++ ++ union{ ++ struct{ ++ void *pFdpShm; ++ uint32_t u32HardwarePageTableCount; ++ HardwarePage_t aHardwarePageTable[64]; ++ volatile uint8_t u8StateBitmap; ++ char PageSpinLockName[256]; ++ RTSPINLOCK PageSpinlock; ++ PfnEntrie_t *pPfnTableR3; ++ PfnEntrie_t *pPfnTableR0; ++ RTSPINLOCK CpuLock; ++ }s; ++ uint8_t padding[4096]; /* Must be page aligned ! */ ++ }mystate; ++ /*ENDMYCODE*/ ++ + /** PGM part. */ + union + { +diff --git a/include/VBox/vmm/vm.mac b/include/VBox/vmm/vm.mac +index e50310d0..8e82b0c8 100644 +--- a/include/VBox/vmm/vm.mac ++++ b/include/VBox/vmm/vm.mac +@@ -49,6 +49,8 @@ struc VMCPU + + alignb 64 + .iem resb 18496 ++ .mystate resb 4096 ++ + + alignb 64 + .idCpu resd 1 +@@ -152,6 +154,8 @@ struc VM + alignb 64 + .cpum resb 1536 + .vmm resb 1600 ++ .bp resb (4096*32) ++ .mystate resb 4096 + .pgm resb (4096*2+6080) + .hm resb 5440 + .trpm resb 5248 +diff --git a/include/VBox/vmm/vmapi.h b/include/VBox/vmm/vmapi.h +index e60024e4..efe909db 100644 +--- a/include/VBox/vmm/vmapi.h ++++ b/include/VBox/vmm/vmapi.h +@@ -500,6 +500,32 @@ VMMR3_INT_DECL(void) VMR3NotifyGlobalFFU(PUVM pUVM, uint32_t fFlags); + VMMR3_INT_DECL(void) VMR3NotifyCpuFFU(PUVMCPU pUVMCpu, uint32_t fFlags); + VMMR3DECL(int) VMR3NotifyCpuDeviceReady(PVM pVM, VMCPUID idCpu); + VMMR3_INT_DECL(int) VMR3WaitHalted(PVM pVM, PVMCPU pVCpu, bool fIgnoreInterrupts); ++/*MYCODE*/ ++VMMDECL(uint8_t) VMR3GetFDPState(PUVM pUVM); ++VMMR3_INT_DECL(int) VMR3AddExecHardBreakpoint(PUVM pUVM, PVMCPU pVCpu, uint64_t GCPtr, uint8_t BreakpointId); ++VMMR3_INT_DECL(int) VMR3AddSoftBreakpoint(PUVM pUVM, PVMCPU pVCpu, uint8_t BreakpointAddressType, uint64_t BreakpointAddress, uint64_t BreakpointCr3); ++VMMR3_INT_DECL(int) VMR3AddPageBreakpoint(PUVM pUVM, PVMCPU pVCpu, uint8_t BreakpointId, uint8_t BreakpointAccessType, uint8_t BreakpointAddressType, uint64_t BreakpointAddress, uint64_t BreakpointLength); ++VMMR3_INT_DECL(int) VMR3AddMsrBreakpoint(PUVM pUVM, uint8_t BreakpointAccessType, uint64_t BreakpointAddress); ++VMMR3_INT_DECL(int) VMR3AddCrBreakpoint(PUVM pUVM, uint8_t BreakpointAccessType, uint64_t BreakpointAddress); ++VMMR3_INT_DECL(void) VMR3ClearBreakpoint(uint8_t BreakpointId); ++VMMR3_INT_DECL(bool) VMR3IsBreakpoint(uint64_t CurrentRIP); ++VMMDECL(int) VMR3PhysSimpleReadGCPhysU(PUVM pUVM, void *pvDst, RTGCPHYS GCPhysSrc, size_t cb); ++VMMDECL(int) VMR3PhysSimpleWriteGCPhysU(PUVM pUVM, const void *pvBuf, RTGCPHYS GCPhys, size_t cbWrite); ++VMMR3_INT_DECL(int) VMR3AddExecPageBreakpoint(PUVM pUVM, PVMCPU pVCpu, uint64_t GCPtr, uint64_t Length); ++VMMR3_INT_DECL(bool) VMR3RemoveBreakpoint(PUVM pUVM, int BreakpointId); ++VMMDECL(int) VMR3SingleStep(PUVM pUVM, PVMCPU pVCpu); ++VMMDECL(int) VMR3Break(PUVM pUVM); ++VMMDECL(int) VMR3Continue(PUVM pUVM); ++VMMDECL(bool) VMR3GetFDPRunning(PUVM pUVM); ++VMMDECL(void) VMR3SetFDPRunning(PUVM pUVM, bool newFDPRunning); ++VMMR3DECL(uint32_t) VMR3GetCPUCount(PUVM pUVM); ++VMMDECL(bool) VMR3HandleSingleStep(PVM pVM, PVMCPU pVCpu); ++VMMDECL(bool) VMR3EnterPause(PVM pVM, PVMCPU pVCpu); ++VMMDECL(void) VMR3SetFDPShm(PUVM pUVM, void *pFdpShm); ++VMMDECL(uint64_t) VMR3Test(PVMCPU pVCpu); ++VMMDECL(int) VMR3InjectInterrupt(PVM pVM, PVMCPU pVCpu, uint32_t enmXcpt, uint32_t uErr, uint64_t Cr2); ++VMMDECL(int) VMR3ClearInterrupt(PUVM pUVM, PVMCPU pVCpu); ++/*ENDMYCODE*/ + VMMR3_INT_DECL(int) VMR3WaitU(PUVMCPU pUVMCpu); + VMMR3DECL(int) VMR3WaitForDeviceReady(PVM pVM, VMCPUID idCpu); + VMMR3_INT_DECL(int) VMR3AsyncPdmNotificationWaitU(PUVMCPU pUVCpu); +diff --git a/include/VBox/vmm/vmm.h b/include/VBox/vmm/vmm.h +index ba7a75dc..c2300726 100644 +--- a/include/VBox/vmm/vmm.h ++++ b/include/VBox/vmm/vmm.h +@@ -289,6 +289,11 @@ VMM_INT_DECL(VMMSWITCHER) VMMGetSwitcher(PVM pVM); + VMM_INT_DECL(bool) VMMIsInRing3Call(PVMCPU pVCpu); + VMM_INT_DECL(void) VMMTrashVolatileXMMRegs(void); + ++/*MYCODE*/ ++VMM_INT_DECL(bool) VMMMatchBreakpointId(PVM pVM, int BreakpointId, RTGCPHYS GCPhys, uint8_t BreakpointType, int BreakpointAccess); ++VMM_INT_DECL(int) VMMGetBreakpointId(PVM pVM, RTGCPHYS GCPhys, uint8_t BreakpointType, int BreakpointAccess); ++VMM_INT_DECL(int) VMMGetBreakpointIdFromPage(PVM pVM, RTGCPHYS GCPhys, uint8_t BreakpointType); ++/*ENDMYCODE*/ + + /** @defgroup grp_vmm_api_r0 The VMM Host Context Ring 0 API + * @{ +@@ -459,6 +464,10 @@ typedef enum VMMR0OPERATION + /** Test the 32->64 bits switcher. */ + VMMR0_DO_TEST_SWITCHER3264, + ++/*MYCODE*/ ++ VMMR0_DO_ALLOC_HCPHYS, ++/*ENDMYCODE*/ ++ + /** The usual 32-bit type blow up. */ + VMMR0_DO_32BIT_HACK = 0x7fffffff + } VMMR0OPERATION; +diff --git a/src/VBox/Debugger/DBGCTcp.cpp b/src/VBox/Debugger/DBGCTcp.cpp +index 15d41dbf..16137da8 100644 +--- a/src/VBox/Debugger/DBGCTcp.cpp ++++ b/src/VBox/Debugger/DBGCTcp.cpp +@@ -30,6 +30,12 @@ + + #include + ++/*MYCODE*/ ++#include ++#include ++#include ++/*MYCODE*/ ++ + + /********************************************************************************************************************************* + * Structures and Typedefs * +@@ -58,7 +64,913 @@ typedef DBGCTCP *PDBGCTCP; + *********************************************************************************************************************************/ + static DECLCALLBACK(int) dbgcTcpConnection(RTSOCKET Sock, void *pvUser); + ++/*MYCODE*/ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++ ++#define MIN(a,b) (((a)<(b))?(a):(b)) ++ ++#define DEBUG_LEVEL 0 ++#define DEBUG_FLOW 0 ++ ++#if DEBUG_LEVEL > 0 ++#define Log1(fmt,...) printf(fmt, ##__VA_ARGS__) ++#else ++#define Log1(fmt,...) ++#endif ++ ++#if DEBUG_LEVEL > 2 ++#define Log3(fmt,...) printf(fmt, ##__VA_ARGS__) ++#else ++#define Log3(fmt,...) ++#endif ++ ++#ifdef DEBUG_FLOW > 0 ++#define LogFloww() printf("%s\n", __FUNCTION__); ++#else ++#define LogFloww() ++#endif ++ ++typedef struct _MEMORY_SSM_T{ ++ uint8_t *pMemory; ++ uint64_t cbMemory; ++ uint64_t CurrentOffset; ++ uint64_t MaxOffset; ++}MEMORY_SSM_T; ++ ++typedef struct FDPVBOX_USERHANDLE_T{ ++ PUVM pUVM; ++ MEMORY_SSM_T* pMemorySSM; ++ FDP_SHM* pFDPServer; ++ uint64_t aVisibleGuestDebugRegisterSave[7]; ++ char TempBuffer[1*1024*1024]; ++}FDPVBOX_USERHANDLE_T; ++ ++bool FDPVBOX_Resume(void *pUserHandle) ++{ ++ LogFlow(("RESUME\n")); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ VMR3Continue(myVBOXHandle->pUVM); ++ return true; ++} ++ ++bool FDPVBOX_Pause(void *pUserHandle) ++{ ++ Log1("PAUSE !\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ VMR3Break(myVBOXHandle->pUVM); ++ return true; ++} ++ ++bool FDPVBOX_singleStep(void *pUserHandle, uint32_t CpuId) ++{ ++ LogFlow(("SINGLE_STEP\n")); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ int rc = VMR3SingleStep(myVBOXHandle->pUVM, pVCpu); ++ if(RT_SUCCESS(rc)){ ++ return true; ++ } ++ return false; ++} ++ ++bool FDPVBOX_getMemorySize(void *pUserHandle, uint64_t* MemorySize) ++{ ++ Log1("GET_PHYSICALMEMORYSIZE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ *MemorySize = MMR3PhysGetRamSizeU(myVBOXHandle->pUVM); ++ return true; ++} ++ ++bool FDPVBOX_readPhysicalMemory(void *pUserHandle, uint8_t *pDstBuffer, uint64_t PhysicalAddress, uint32_t ReadSize) ++{ ++ Log1("READ_PHYSICAL %p %d ... ", PhysicalAddress, ReadSize); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ int rc = VMR3PhysSimpleReadGCPhysU(myVBOXHandle->pUVM, pDstBuffer, PhysicalAddress, ReadSize); ++ Log1(" %s\n", RT_SUCCESS(rc) ? "OK" : "KO"); ++ if(RT_SUCCESS(rc)){ ++ return true; ++ } ++ return false; ++} ++ ++ ++bool FDPVBOX_writePhysicalMemory(void *pUserHandle, uint8_t *pSrcBuffer, uint64_t PhysicalAddress, uint32_t WriteSize) ++{ ++ Log1("WRITE_PHYSICAL %p %d...", PhysicalAddress, WriteSize); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ ++ //Check Read access ++ if(FDPVBOX_readPhysicalMemory(pUserHandle, (uint8_t*)myVBOXHandle->TempBuffer, PhysicalAddress, WriteSize) == false){ ++ return false; ++ } ++ //Effective Write ++ int rc = VMR3PhysSimpleWriteGCPhysU(myVBOXHandle->pUVM, pSrcBuffer, PhysicalAddress, WriteSize); ++ Log1(" %s\n", RT_SUCCESS(rc) ? "OK" : "KO"); ++ if(RT_SUCCESS(rc)){ ++ return true; ++ } ++ return false; ++} ++ ++ ++bool FDPVBOX_writeVirtualMemory(void *pUserHandle, uint32_t CpuId, uint8_t *pSrcBuffer, uint64_t VirtualAddress, uint32_t WriteSize) ++{ ++ Log1("writeVirtualMemory %p %d ...", VirtualAddress, WriteSize); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ int rc = PGMPhysSimpleWriteGCPtr(pVCpu, VirtualAddress, pSrcBuffer, WriteSize); ++ Log1(" %s\n", RT_SUCCESS(rc) ? "OK" : "KO"); ++ if(RT_SUCCESS(rc)){ ++ return true; ++ } ++ return false; ++} ++ ++bool FDPVBOX_writeMsr(void *pUserHandle, uint32_t CpuId, uint64_t MSRId, uint64_t MSRValue) ++{ ++ Log1("WRITE_MSR %p %p\n", MSRId, MSRValue); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ CPUMSetGuestMsr(pVCpu, MSRId, MSRValue); ++ //if(RT_SUCCESS(rc)){ ++ // return true; ++ //} ++ return false; ++} ++ ++bool FDPVBOX_getState(void *pUserHandle, uint8_t *currentState) ++{ ++ Log3("GET_STATE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ *currentState = VMR3GetFDPState(myVBOXHandle->pUVM); ++ return true; ++} ++ ++bool FDPVBOX_getCpuState(void *pUserHandle, uint32_t CpuId, uint8_t *pCurrentState) ++{ ++ Log1("GET_CPU_STATE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ *pCurrentState = pVCpu->mystate.s.u8StateBitmap; ++ return true; ++} ++ ++ ++bool FDPVBOX_getCpuCount(void *pUserHandle, uint32_t *pCpuCount) ++{ ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ *pCpuCount = VMR3GetCPUCount(myVBOXHandle->pUVM); ++ return true; ++} ++ ++bool FDPVBOX_readMsr(void *pUserHandle, uint32_t CpuId, uint64_t MsrId, uint64_t *pMsrValue) ++{ ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ CPUMQueryGuestMsr(pVCpu, MsrId, pMsrValue); ++ Log1("READ_MSR %p => %p\n", MsrId, *pMsrValue); ++ return true; ++} ++ ++bool FDPVBOX_readRegister(void *pUserHandle, uint32_t CpuId, FDP_Register RegisterId, uint64_t *pRegisterValue) ++{ ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ PCCPUMCTXCORE pCtxCore = CPUMGetGuestCtxCore(pVCpu); ++ PCPUMCTXCORE pRegFrame = (PCPUMCTXCORE)CPUMGetGuestCtxCore(pVCpu); ++ ++ switch(RegisterId){ ++ case FDP_CR0_REGISTER: *pRegisterValue = CPUMGetGuestCR0(pVCpu); break; ++ case FDP_CR2_REGISTER: *pRegisterValue = CPUMGetGuestCR2(pVCpu); break; ++ case FDP_CR3_REGISTER: *pRegisterValue = CPUMGetGuestCR3(pVCpu); break; ++ case FDP_CR4_REGISTER: *pRegisterValue = CPUMGetGuestCR4(pVCpu); break; ++ case FDP_CR8_REGISTER: *pRegisterValue = CPUMGetGuestCR8(pVCpu); break; ++ case FDP_RAX_REGISTER: *pRegisterValue = pCtxCore->rax; break; ++ case FDP_RBX_REGISTER: *pRegisterValue = pCtxCore->rbx; break; ++ case FDP_RCX_REGISTER: *pRegisterValue = pCtxCore->rcx; break; ++ case FDP_RDX_REGISTER: *pRegisterValue = pCtxCore->rdx; break; ++ case FDP_R8_REGISTER: *pRegisterValue = pCtxCore->r8; break; ++ case FDP_R9_REGISTER: *pRegisterValue = pCtxCore->r9; break; ++ case FDP_R10_REGISTER: *pRegisterValue = pCtxCore->r10; break; ++ case FDP_R11_REGISTER: *pRegisterValue = pCtxCore->r11; break; ++ case FDP_R12_REGISTER: *pRegisterValue = pCtxCore->r12; break; ++ case FDP_R13_REGISTER: *pRegisterValue = pCtxCore->r13; break; ++ case FDP_R14_REGISTER: *pRegisterValue = pCtxCore->r14; break; ++ case FDP_R15_REGISTER: *pRegisterValue = pCtxCore->r15; break; ++ case FDP_RSP_REGISTER: *pRegisterValue = pCtxCore->rsp; break; ++ case FDP_RBP_REGISTER: *pRegisterValue = pCtxCore->rbp; break; ++ case FDP_RSI_REGISTER: *pRegisterValue = pCtxCore->rsi; break; ++ case FDP_RDI_REGISTER: *pRegisterValue = pCtxCore->rdi; break; ++ case FDP_RIP_REGISTER: *pRegisterValue = pCtxCore->rip; break; ++ ++ //Visible for Guest Debug Register ++ case FDP_VDR0_REGISTER: *pRegisterValue = pVCpu->mystate.s.aGuestDr[0]; break; ++ case FDP_VDR1_REGISTER: *pRegisterValue = pVCpu->mystate.s.aGuestDr[1]; break; ++ case FDP_VDR2_REGISTER: *pRegisterValue = pVCpu->mystate.s.aGuestDr[2]; break; ++ case FDP_VDR3_REGISTER: *pRegisterValue = pVCpu->mystate.s.aGuestDr[3]; break; ++ case FDP_VDR6_REGISTER: *pRegisterValue = pVCpu->mystate.s.aGuestDr[6]; break; ++ case FDP_VDR7_REGISTER: *pRegisterValue = pVCpu->mystate.s.aGuestDr[7]; break; ++ ++ //Invisible for Guest Debug Register ++ case FDP_DR0_REGISTER: *pRegisterValue = CPUMGetGuestDR0(pVCpu); break; ++ case FDP_DR1_REGISTER: *pRegisterValue = CPUMGetGuestDR1(pVCpu); break; ++ case FDP_DR2_REGISTER: *pRegisterValue = CPUMGetGuestDR2(pVCpu); break; ++ case FDP_DR3_REGISTER: *pRegisterValue = CPUMGetGuestDR3(pVCpu); break; ++ case FDP_DR6_REGISTER: *pRegisterValue = CPUMGetGuestDR6(pVCpu); break; ++ case FDP_DR7_REGISTER: *pRegisterValue = CPUMGetGuestDR7(pVCpu); break; ++ ++ case FDP_CS_REGISTER: *pRegisterValue = CPUMGetGuestCS(pVCpu); break; ++ case FDP_DS_REGISTER: *pRegisterValue = CPUMGetGuestDS(pVCpu); break; ++ case FDP_ES_REGISTER: *pRegisterValue = CPUMGetGuestES(pVCpu); break; ++ case FDP_FS_REGISTER: *pRegisterValue = CPUMGetGuestFS(pVCpu); break; ++ case FDP_GS_REGISTER: *pRegisterValue = CPUMGetGuestGS(pVCpu); break; ++ case FDP_SS_REGISTER: *pRegisterValue = CPUMGetGuestSS(pVCpu); break; ++ case FDP_RFLAGS_REGISTER: *pRegisterValue = CPUMGetGuestEFlags(pVCpu); break; ++ case FDP_GDTRB_REGISTER: ++ { ++ VBOXGDTR gdtr = {0, 0}; ++ CPUMGetGuestGDTR(pVCpu,&gdtr); ++ *pRegisterValue = gdtr.pGdt; ++ break; ++ } ++ case FDP_GDTRL_REGISTER: ++ { ++ VBOXGDTR gdtr = {0, 0}; ++ CPUMGetGuestGDTR(pVCpu,&gdtr); ++ *pRegisterValue = gdtr.cbGdt; ++ break; ++ } ++ case FDP_IDTRB_REGISTER: ++ { ++ uint16_t cbIDT; ++ RTGCPTR GCPtrIDT = (RTGCPTR)CPUMGetGuestIDTR(pVCpu, &cbIDT); ++ *pRegisterValue = GCPtrIDT; ++ break; ++ } ++ case FDP_IDTRL_REGISTER: ++ { ++ uint16_t cbIDT; ++ RTGCPTR GCPtrIDT = (RTGCPTR)CPUMGetGuestIDTR(pVCpu, &cbIDT); ++ *pRegisterValue = cbIDT; ++ break; ++ } ++ case FDP_LDTR_REGISTER: ++ { ++ uint64_t Ldtrb; ++ uint32_t Ldtrl; ++ *pRegisterValue = CPUMGetGuestLdtrEx(pVCpu, &Ldtrb, &Ldtrl); ++ break; ++ } ++ case FDP_LDTRB_REGISTER: ++ { ++ uint64_t Ldtrb; ++ uint32_t Ldtrl; ++ CPUMGetGuestLdtrEx(pVCpu, &Ldtrb, &Ldtrl); ++ *pRegisterValue = Ldtrb; ++ break; ++ } ++ case FDP_LDTRL_REGISTER: ++ { ++ uint64_t Ldtrb; ++ uint32_t Ldtrl; ++ CPUMGetGuestLdtrEx(pVCpu, &Ldtrb, &Ldtrl); ++ *pRegisterValue = Ldtrl; ++ break; ++ } ++ case FDP_TR_REGISTER: ++ { ++ *pRegisterValue = CPUMGetGuestTR(pVCpu, NULL); ++ break; ++ } ++ default: ++ { ++ *pRegisterValue = 0xBADBADBADBADBADB; ++ return false; ++ } ++ } ++ return true; ++} ++ ++bool FDPVBOX_writeRegister(void *pUserHandle, uint32_t CpuId, FDP_Register RegisterId, uint64_t RegisterValue) ++{ ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ PCCPUMCTXCORE pCtxCore = CPUMGetGuestCtxCore(pVCpu); ++ PCPUMCTXCORE pRegFrame = (PCPUMCTXCORE)CPUMGetGuestCtxCore(pVCpu); ++ ++ FDP_CPU_CTX* pFdpCpuCtx = (FDP_CPU_CTX *)pVCpu->mystate.s.pCpuShm; ++ ++ switch(RegisterId){ ++ case FDP_RAX_REGISTER: pRegFrame->rax = RegisterValue; pFdpCpuCtx->rax = RegisterValue; break; ++ case FDP_RBX_REGISTER: pRegFrame->rbx = RegisterValue; pFdpCpuCtx->rbx = RegisterValue; break; ++ case FDP_RCX_REGISTER: pRegFrame->rcx = RegisterValue; pFdpCpuCtx->rcx = RegisterValue; break; ++ case FDP_RDX_REGISTER: pRegFrame->rdx = RegisterValue; pFdpCpuCtx->rdx = RegisterValue; break; ++ case FDP_R8_REGISTER: pRegFrame->r8 = RegisterValue; pFdpCpuCtx->r8 = RegisterValue; break; ++ case FDP_R9_REGISTER: pRegFrame->r9 = RegisterValue; pFdpCpuCtx->r9 = RegisterValue; break; ++ case FDP_R10_REGISTER: pRegFrame->r10 = RegisterValue; pFdpCpuCtx->r10 = RegisterValue; break; ++ case FDP_R11_REGISTER: pRegFrame->r11 = RegisterValue; pFdpCpuCtx->r11 = RegisterValue; break; ++ case FDP_R12_REGISTER: pRegFrame->r12 = RegisterValue; pFdpCpuCtx->r12 = RegisterValue; break; ++ case FDP_R13_REGISTER: pRegFrame->r13 = RegisterValue; pFdpCpuCtx->r13 = RegisterValue; break; ++ case FDP_R14_REGISTER: pRegFrame->r14 = RegisterValue; pFdpCpuCtx->r14 = RegisterValue; break; ++ case FDP_R15_REGISTER: pRegFrame->r15 = RegisterValue; pFdpCpuCtx->r15 = RegisterValue; break; ++ case FDP_RSP_REGISTER: pRegFrame->rsp = RegisterValue; pFdpCpuCtx->rsp = RegisterValue; break; ++ case FDP_RBP_REGISTER: pRegFrame->rbp = RegisterValue; pFdpCpuCtx->rbp = RegisterValue; break; ++ case FDP_RSI_REGISTER: pRegFrame->rsi = RegisterValue; pFdpCpuCtx->rsi = RegisterValue; break; ++ case FDP_RDI_REGISTER: pRegFrame->rdi = RegisterValue; pFdpCpuCtx->rdi = RegisterValue; break; ++ case FDP_RIP_REGISTER: pRegFrame->rip = RegisterValue; pFdpCpuCtx->rip = RegisterValue; break; ++ ++ //Invisible for Guest Debug Register ++ case FDP_DR0_REGISTER: CPUMSetGuestDR0(pVCpu, RegisterValue); break; ++ case FDP_DR1_REGISTER: CPUMSetGuestDR1(pVCpu, RegisterValue); break; ++ case FDP_DR2_REGISTER: CPUMSetGuestDR2(pVCpu, RegisterValue); break; ++ case FDP_DR3_REGISTER: CPUMSetGuestDR3(pVCpu, RegisterValue); break; ++ case FDP_DR6_REGISTER: CPUMSetGuestDR6(pVCpu, RegisterValue); break; ++ case FDP_DR7_REGISTER: CPUMSetGuestDR7(pVCpu, RegisterValue); break; ++ ++ //Visible for Guest Debug Register ++ case FDP_VDR0_REGISTER: pVCpu->mystate.s.aGuestDr[0] = RegisterValue; break; ++ case FDP_VDR1_REGISTER: pVCpu->mystate.s.aGuestDr[1] = RegisterValue; break; ++ case FDP_VDR2_REGISTER: pVCpu->mystate.s.aGuestDr[2] = RegisterValue; break; ++ case FDP_VDR3_REGISTER: pVCpu->mystate.s.aGuestDr[3] = RegisterValue; break; ++ case FDP_VDR6_REGISTER: pVCpu->mystate.s.aGuestDr[6] = RegisterValue; break; ++ case FDP_VDR7_REGISTER: pVCpu->mystate.s.aGuestDr[7] = RegisterValue; break; ++ ++ case FDP_CS_REGISTER: CPUMSetGuestCS(pVCpu, RegisterValue); break; ++ case FDP_DS_REGISTER: CPUMSetGuestDS(pVCpu, RegisterValue); break; ++ case FDP_ES_REGISTER: CPUMSetGuestES(pVCpu, RegisterValue); break; ++ case FDP_FS_REGISTER: CPUMSetGuestFS(pVCpu, RegisterValue); break; ++ case FDP_GS_REGISTER: CPUMSetGuestGS(pVCpu, RegisterValue); break; ++ case FDP_SS_REGISTER: CPUMSetGuestSS(pVCpu, RegisterValue); break; ++ case FDP_CR0_REGISTER: CPUMSetGuestCR0(pVCpu, RegisterValue); pFdpCpuCtx->cr0 = RegisterValue; break; ++ case FDP_CR2_REGISTER: CPUMSetGuestCR2(pVCpu, RegisterValue); pFdpCpuCtx->cr3 = RegisterValue; break; ++ case FDP_CR3_REGISTER: ++ { ++ CPUMSetGuestCR3(pVCpu, RegisterValue); ++ PGMFlushTLB(pVCpu, RegisterValue, 0); ++ pFdpCpuCtx->cr3 = RegisterValue; ++ break; ++ } ++ case FDP_CR4_REGISTER: CPUMSetGuestCR4(pVCpu, RegisterValue); pFdpCpuCtx->cr4 = RegisterValue; break; ++ //case FDP_CR8_REGISTER: CPUMSetGuestCR8(pVCpu, RegisterValue); break; ++ case FDP_RFLAGS_REGISTER: CPUMSetGuestEFlags(pVCpu, RegisterValue); break; ++ default: break; ++ } ++ return true; ++} ++ ++bool FDPVBOX_virtualToPhysical(void *pUserHandle, uint32_t CpuId, uint64_t VirtualAddress, uint64_t *PhysicalAddress) ++{ ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ //int rc = PGMPhysGCPtr2GCPhys(pVCpu, VirtualAddress, PhysicalAddress); ++ int rc = PGMGstGetPage(pVCpu, VirtualAddress, NULL, PhysicalAddress); ++ if(RT_FAILURE(rc)){ ++ return false; ++ } ++ return true; ++} ++ ++bool FDPVBOX_unsetBreakpoint(void *pUserHandle, uint8_t BreakpointId) ++{ ++ Log1("UNSET_BP [%d] ! \n", BreakpointId); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ int rc = VMR3RemoveBreakpoint(myVBOXHandle->pUVM, BreakpointId); ++ if(RT_SUCCESS(rc)){ ++ return true; ++ } ++ return false; ++} ++ ++bool FDPVBOX_getFxState64(void *pUserHandle, uint32_t CpuId, uint8_t *pDstBuffer, uint32_t *pDstSize) ++{ ++ Log1("GET_FXSTATE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ PCPUMCTX pCtx = CPUMQueryGuestCtxPtr(pVCpu); ++ PX86FXSTATE pFpuCtx = &pCtx->CTX_SUFF(pXState)->x87; ++ memcpy(pDstBuffer, pFpuCtx, sizeof(X86FXSTATE)); ++ *pDstSize = sizeof(X86FXSTATE); ++ return true; ++} ++ ++bool FDPVBOX_setFxState64(void *pUserHandle, uint32_t CpuId, uint8_t *pSrcBuffer, uint32_t uSrcSize) ++{ ++ Log1("SET_FXSTATE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ PCPUMCTX pCtx = CPUMQueryGuestCtxPtr(pVCpu); ++ PX86FXSTATE pFpuCtx = &pCtx->CTX_SUFF(pXState)->x87; ++ memcpy(pFpuCtx, pSrcBuffer, sizeof(X86FXSTATE)); ++ return true; ++} ++ ++bool FDPVBOX_readVirtualMemory(void *pUserHandle, uint32_t CpuId, uint64_t VirtualAddress, uint32_t ReadSize, uint8_t *pDstBuffer) ++{ ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return false; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ int rc = 0; ++ rc = PGMPhysSimpleReadGCPtr(pVCpu, pDstBuffer, VirtualAddress, ReadSize); ++ if(RT_SUCCESS(rc)){ ++ return true; ++ } ++ return false; ++} ++ ++int FDPVBOX_setBreakpoint( ++ void *pUserHandle, ++ uint32_t CpuId, ++ FDP_BreakpointType BreakpointType, ++ uint8_t BreakpointId, ++ FDP_Access BreakpointAccessType, ++ FDP_AddressType BreakpointAddressType, ++ uint64_t BreakpointAddress, ++ uint64_t BreakpointLength, ++ uint64_t BreakpointCr3) ++{ ++ Log1("SET_BREAKPOINT %p\n", BreakpointAddress); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ if(CpuId >= VMR3GetCPUCount(myVBOXHandle->pUVM)){ ++ return -1; ++ } ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(myVBOXHandle->pUVM, CpuId); ++ ++ BreakpointId = -1; ++ switch(BreakpointType){ ++ case FDP_SOFTHBP: ++ { ++ BreakpointId = VMR3AddSoftBreakpoint(myVBOXHandle->pUVM, pVCpu, BreakpointAddressType, BreakpointAddress, BreakpointCr3); ++ Log1("FDP_SOFTHBP[%d] %c %p %p\n", BreakpointId, BreakpointAddressType == 0x1 ? 'v' : 'p', BreakpointAddress, BreakpointCr3); ++ break; ++ } ++ case FDP_PAGEHBP: ++ { ++ BreakpointId = VMR3AddPageBreakpoint(myVBOXHandle->pUVM, pVCpu, -1, BreakpointAccessType, BreakpointAddressType, BreakpointAddress, BreakpointLength); ++ Log1("FDP_PAGEHBP[%d] %02x %p\n", BreakpointId, BreakpointAccessType, BreakpointAddress); ++ break; ++ } ++ case FDP_MSRHBP: ++ { ++ BreakpointId = VMR3AddMsrBreakpoint(myVBOXHandle->pUVM, BreakpointAccessType, BreakpointAddress); ++ Log1("FDP_MSRHBP[%d] %02x %p\n", BreakpointId, BreakpointAccessType, BreakpointAddress); ++ break; ++ } ++ case FDP_CRHBP: ++ { ++ BreakpointId = VMR3AddCrBreakpoint(myVBOXHandle->pUVM, BreakpointAccessType, BreakpointAddress); ++ Log1("FDP_CRHBP[%d] %02x %p\n", BreakpointId, BreakpointAccessType, BreakpointAddress); ++ break; ++ } ++ default: ++ { ++ Log1("Unknown BreakpointType!\n"); ++ break; ++ } ++ } ++ ++ return BreakpointId; ++} ++ ++ ++bool FDPVBOX_InjectInterrupt(void *pUserHandle, uint32_t CpuId, uint32_t InterruptionCode, uint32_t ErrorCode, uint64_t Cr2){ ++ Log1("InjectInterrupt\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ PUVM pUVM = myVBOXHandle->pUVM; ++ ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, 0); ++ ++ VMR3InjectInterrupt(NULL, pVCpu, InterruptionCode, ErrorCode, Cr2); ++ return true; ++} ++ ++bool FDPVBOX_Reboot(void *pUserHandle) ++{ ++ Log1("REBOOT\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ PUVM pUVM = myVBOXHandle->pUVM; ++ ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, 0); ++ ++ FDPVBOX_Pause(pUserHandle); ++ for(int BreakpointId = 0; BreakpointId < FDP_MAX_BREAKPOINT; BreakpointId++){ ++ FDPVBOX_unsetBreakpoint(pUserHandle, BreakpointId); ++ } ++ CPUMSetGuestDR7(pVCpu, 0); ++ ++ //Ask the EMT the Triple fault ++ pVCpu->mystate.s.bRebootRequired = true; ++ ++ FDPVBOX_Resume(pUserHandle); ++ ++ //TODO: Wait for the startup ++ usleep(100 * 1000); ++ ++ //Signal that the VM as changed, and what a change... ++ myVBOXHandle->pFDPServer->pSharedFDPSHM->stateChanged = true; ++ ++ return true; ++} ++ ++ ++ ++#include ++#include ++ ++ ++ ++static DECLCALLBACK(int) nullProgressCallback(PUVM pUVM, unsigned uPercent, void *pvUser) ++{ ++ NOREF(pUVM); ++ return VINF_SUCCESS; ++} ++ ++static DECLCALLBACK(int) pfnMemoryWrite(void *pvUser, uint64_t offStream, const void *pvBuf, size_t cbToWrite) ++{ ++ MEMORY_SSM_T* pMemorySSM = (MEMORY_SSM_T*)pvUser; ++ memcpy(pMemorySSM->pMemory+offStream, pvBuf, cbToWrite); ++ pMemorySSM->CurrentOffset = offStream; ++ if(offStream+cbToWrite > pMemorySSM->MaxOffset){ ++ pMemorySSM->MaxOffset = offStream+cbToWrite; ++ } ++ return VINF_SUCCESS; ++} ++ ++static DECLCALLBACK(int) pfnMemoryRead(void *pvUser, uint64_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead) ++{ ++ MEMORY_SSM_T* pMemorySSM = (MEMORY_SSM_T*)pvUser; ++ memcpy(pvBuf, pMemorySSM->pMemory+offStream, cbToRead); ++ *pcbRead = cbToRead; ++ pMemorySSM->CurrentOffset = offStream + cbToRead; ++ return VINF_SUCCESS; ++} ++ ++static DECLCALLBACK(int) pfnMemorySeek(void *pvUser, int64_t offSeek, unsigned uMethod, uint64_t *poffActual) ++{ ++ MEMORY_SSM_T* pMemorySSM = (MEMORY_SSM_T*)pvUser; ++ if(uMethod == RTFILE_SEEK_BEGIN){ ++ pMemorySSM->CurrentOffset = offSeek; ++ } ++ if(uMethod == RTFILE_SEEK_END){ ++ pMemorySSM->CurrentOffset = pMemorySSM->cbMemory-offSeek; ++ } ++ if(uMethod == RTFILE_SEEK_CURRENT){ ++ pMemorySSM->CurrentOffset += offSeek; ++ } ++ *poffActual = pMemorySSM->CurrentOffset; ++ if(*poffActual > pMemorySSM->MaxOffset){ ++ pMemorySSM->MaxOffset = *poffActual; ++ } ++ return VINF_SUCCESS; ++} ++ ++static DECLCALLBACK(uint64_t) pfnMemoryTell(void *pvUser) ++{ ++ MEMORY_SSM_T* pMemorySSM = (MEMORY_SSM_T*)pvUser; ++ return pMemorySSM->CurrentOffset; ++} ++ ++static DECLCALLBACK(int) pfnMemorySize(void *pvUser, uint64_t *pcb) ++{ ++ MEMORY_SSM_T* pMemorySSM = (MEMORY_SSM_T*)pvUser; ++ *pcb = pMemorySSM->MaxOffset; ++ return VINF_SUCCESS; ++} ++ ++static DECLCALLBACK(int) pfnMemoryIsOk(void *pvUser){ ++ return VINF_SUCCESS; ++} ++ ++static DECLCALLBACK(int) pfnMemoryClose(void *pvUser, bool fCancelled) ++{ ++ MEMORY_SSM_T* pMemorySSM = (MEMORY_SSM_T*)pvUser; ++ pMemorySSM->CurrentOffset = 0; ++ pMemorySSM->MaxOffset = 0; ++ return VINF_SUCCESS; ++} ++ ++static SSMSTRMOPS const g_ftmR3MemoryOps = ++{ ++ SSMSTRMOPS_VERSION, ++ pfnMemoryWrite, ++ pfnMemoryRead, ++ pfnMemorySeek, ++ pfnMemoryTell, ++ pfnMemorySize, ++ pfnMemoryIsOk, ++ pfnMemoryClose, ++ SSMSTRMOPS_VERSION ++}; ++ ++ ++ ++bool FDPVBOX_Save(void *pUserHandle) ++{ ++ Log1("SAVE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ PUVM pUVM = myVBOXHandle->pUVM; ++ ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, 0); ++ ++ //Avoid Interrupt during save, we don't want Interrupt in our save state ++ pVCpu->mystate.s.bDisableInterrupt = true; + ++ //Ask all CPU to suspend ++ printf("Save.FDPVBOX_Pause\n"); ++ FDPVBOX_Pause(pUserHandle); ++ ++ printf("Save.UsetBreakpoint\n"); ++ for(int BreakpointId = 0; BreakpointId < FDP_MAX_BREAKPOINT; BreakpointId++){ ++ FDPVBOX_unsetBreakpoint(pUserHandle, BreakpointId); ++ } ++ //Disable Hardware breakpoint ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR0_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR1_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR2_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR3_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR6_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR7_REGISTER, 0); ++ ++ FDPVBOX_readRegister(pUserHandle, 0, FDP_VDR0_REGISTER, &myVBOXHandle->aVisibleGuestDebugRegisterSave[0]); ++ FDPVBOX_readRegister(pUserHandle, 0, FDP_VDR1_REGISTER, &myVBOXHandle->aVisibleGuestDebugRegisterSave[1]); ++ FDPVBOX_readRegister(pUserHandle, 0, FDP_VDR2_REGISTER, &myVBOXHandle->aVisibleGuestDebugRegisterSave[2]); ++ FDPVBOX_readRegister(pUserHandle, 0, FDP_VDR3_REGISTER, &myVBOXHandle->aVisibleGuestDebugRegisterSave[3]); ++ FDPVBOX_readRegister(pUserHandle, 0, FDP_VDR6_REGISTER, &myVBOXHandle->aVisibleGuestDebugRegisterSave[6]); ++ FDPVBOX_readRegister(pUserHandle, 0, FDP_VDR7_REGISTER, &myVBOXHandle->aVisibleGuestDebugRegisterSave[7]); ++ ++ for(uint32_t i=0; imystate.s.bSuspendRequired = true; ++ } ++ ++ //Resume all CPU for suspend ++ printf("Save.FDPVBOX_Resume\n"); ++ FDPVBOX_Resume(pUserHandle); ++ ++ //Suspend all CPU ++ printf("Save.VMR3Suspend\n"); ++ VMR3Suspend(pUVM, VMSUSPENDREASON_USER); ++ for(uint32_t i=0; imystate.s.bSuspendRequired = false; ++ } ++ ++ //Alloc SaveState memory ++ if(myVBOXHandle->pMemorySSM->pMemory == NULL){ ++ myVBOXHandle->pMemorySSM->cbMemory = MMR3PhysGetRamSizeU(pUVM); ++ myVBOXHandle->pMemorySSM->pMemory = (uint8_t*)malloc(myVBOXHandle->pMemorySSM->cbMemory); ++ } ++ ++ //Set offset ++ myVBOXHandle->pMemorySSM->CurrentOffset = 0; ++ myVBOXHandle->pMemorySSM->MaxOffset = 0; ++ ++ //Save state ++ printf("Save.VMR3SaveFT\n"); ++ bool bSuspended = false; ++ VMR3SaveFT(pUVM, &g_ftmR3MemoryOps, (void*)myVBOXHandle->pMemorySSM, &bSuspended, true); ++ ++ for(uint32_t i=0; imystate.s.bRestoreRequired = true; ++ } ++ ++ printf("Save.VMR3Resume\n"); ++ VMR3Resume(pUVM, VMRESUMEREASON_STATE_RESTORED); ++ ++ printf("Save.FDPVBOX_Pause\n"); ++ FDPVBOX_Pause(pUserHandle); ++ ++ for(uint32_t i=0; imystate.s.bRestoreRequired = false; ++ } ++ ++ pVCpu->mystate.s.bDisableInterrupt = false; ++ ++ return true; ++} ++ ++bool FDPVBOX_Restore(void *pUserHandle) ++{ ++ Log1("RESTORE\n"); ++ FDPVBOX_USERHANDLE_T* myVBOXHandle = (FDPVBOX_USERHANDLE_T*)pUserHandle; ++ PUVM pUVM = myVBOXHandle->pUVM; ++ int rc; ++ if(myVBOXHandle->pMemorySSM->pMemory != NULL){ ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, 0); ++ ++ //Avoid Interrupt during save, we don't want Interrupt in our save state ++ pVCpu->mystate.s.bDisableInterrupt = true; ++ ++ printf("Restore.Pause\n"); ++ FDPVBOX_Pause(pUserHandle); ++ ++ printf("Restore.UsetBreakpoint\n"); ++ for(int BreakpointId = 0; BreakpointId < FDP_MAX_BREAKPOINT; BreakpointId++){ ++ FDPVBOX_unsetBreakpoint(pUserHandle, BreakpointId); ++ } ++ //Disable Hardware breakpoint ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR0_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR1_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR2_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR3_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR6_REGISTER, 0); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_DR7_REGISTER, 0); ++ ++ printf("Restore.FDPVBOX_Resume\n"); ++ //Force console client to reconnect ++ FDPVBOX_Resume(pUserHandle); ++ ++ printf("Restore.VMR3Reset\n"); ++ VMR3Reset(pUVM); ++ ++ usleep(500 * 1000); ++ ++ printf("Restore.VMR3Suspend\n"); ++ rc = VMR3Suspend(pUVM, VMSUSPENDREASON_USER); ++ ++ ++ //rc = VMR3Suspend(pUVM, VMSUSPENDREASON_USER); ++ //printf("%d\n", rc); ++ ++ printf("Restore.VMR3LoadFromStream\n"); ++ VMR3LoadFromStream(pUVM, &g_ftmR3MemoryOps, (void*)myVBOXHandle->pMemorySSM, nullProgressCallback, NULL); ++ ++ printf("Restore.VMR3Resume\n"); ++ pVCpu->mystate.s.bRestoreRequired = true; ++ VMR3Resume(pUVM, VMRESUMEREASON_STATE_RESTORED); ++ ++ printf("Restore.FDPVBOX_Pause\n"); ++ FDPVBOX_Pause(pUserHandle); ++ pVCpu->mystate.s.bRestoreRequired = false; ++ ++ //Restore visible for Guest Debug Register ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_VDR0_REGISTER, myVBOXHandle->aVisibleGuestDebugRegisterSave[0]); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_VDR1_REGISTER, myVBOXHandle->aVisibleGuestDebugRegisterSave[1]); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_VDR2_REGISTER, myVBOXHandle->aVisibleGuestDebugRegisterSave[2]); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_VDR3_REGISTER, myVBOXHandle->aVisibleGuestDebugRegisterSave[3]); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_VDR6_REGISTER, myVBOXHandle->aVisibleGuestDebugRegisterSave[6]); ++ FDPVBOX_writeRegister(pUserHandle, 0, FDP_VDR7_REGISTER, myVBOXHandle->aVisibleGuestDebugRegisterSave[7]); ++ ++ pVCpu->mystate.s.bDisableInterrupt = false; ++ ++ //printf("%d\n", VMR3ClearInterrupt(pUVM, NULL)); ++ ++ printf("Restore.Done!\n"); ++ return true; ++ } ++ ++ return false; ++} ++ ++void *CreateCPUSHM(PUVM pUVM) ++{ ++ int hMapFile; ++ void* pBuf; ++ ++ char aCpuShmName[512] = {0}; ++ strcpy(aCpuShmName,"CPU_"); ++ strcat(aCpuShmName, VMR3GetName(pUVM)); ++ ++ pBuf = CreateSHM(aCpuShmName, sizeof(FDP_CPU_CTX)); ++ if (pBuf == NULL) { ++ return NULL; ++ } ++ //Clear SHM ++ memset((void*)pBuf, 0, sizeof(FDP_CPU_CTX)); ++ ++ printf("0x%p\n", sizeof(FDP_CPU_CTX)); ++ ++ return pBuf; ++} ++ ++void* FDPServerThread(LPVOID lpParam) ++{ ++ PUVM pUVM = (PUVM)lpParam; ++ MEMORY_SSM_T MemorySSM; ++ MemorySSM.pMemory = NULL; ++ MemorySSM.CurrentOffset = 0; ++ ++ FDP_SHM* pFDPServer = FDP_CreateSHM((char*)VMR3GetName(pUVM)); ++ if(pFDPServer == NULL){ ++ printf("FDP SHM creation failed !\n"); ++ return NULL; ++ } ++ ++ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, 0); ++ //PCPUMCTX pCtx = CPUMQueryGuestCtxPtr(pVCpu); ++ void* pCpuShm = CreateCPUSHM(pUVM); ++ pVCpu->mystate.s.pCpuShm = pCpuShm; ++ if(pCpuShm == NULL){ ++ printf("Failed to CreateCpuShm\n"); ++ return NULL; ++ } ++ ++ printf("FDP_CreateSHM OK\n"); ++ FDPVBOX_USERHANDLE_T *pUserHandle = (FDPVBOX_USERHANDLE_T*)malloc(sizeof(FDPVBOX_USERHANDLE_T)); ++ pUserHandle->pUVM = pUVM; ++ pUserHandle->pMemorySSM = &MemorySSM; ++ pUserHandle->pFDPServer = pFDPServer; ++ ++ //Configure FDP Server Interface ++ FDP_SERVER_INTERFACE_T FDPServerInterface; ++ FDPServerInterface.pUserHandle = pUserHandle; ++ ++ FDPServerInterface.pfnGetState = &FDPVBOX_getState; ++ FDPServerInterface.pfnReadRegister = &FDPVBOX_readRegister; ++ FDPServerInterface.pfnWriteRegister = &FDPVBOX_writeRegister; ++ FDPServerInterface.pfnWritePhysicalMemory = &FDPVBOX_writePhysicalMemory; ++ FDPServerInterface.pfnWriteVirtualMemory = &FDPVBOX_writeVirtualMemory; ++ FDPServerInterface.pfnGetMemorySize = &FDPVBOX_getMemorySize; ++ FDPServerInterface.pfnResume = &FDPVBOX_Resume; ++ FDPServerInterface.pfnSingleStep = &FDPVBOX_singleStep; ++ FDPServerInterface.pfnPause = &FDPVBOX_Pause; ++ FDPServerInterface.pfnReadMsr = &FDPVBOX_readMsr; ++ FDPServerInterface.pfnWriteMsr = &FDPVBOX_writeMsr; ++ FDPServerInterface.pfnGetCpuCount = &FDPVBOX_getCpuCount; ++ FDPServerInterface.pfnGetCpuState = &FDPVBOX_getCpuState; ++ FDPServerInterface.pfnVirtualToPhysical = &FDPVBOX_virtualToPhysical; ++ FDPServerInterface.pfnUnsetBreakpoint = &FDPVBOX_unsetBreakpoint; ++ FDPServerInterface.pfnGetFxState64 = &FDPVBOX_getFxState64; ++ FDPServerInterface.pfnSetFxState64 = &FDPVBOX_setFxState64; ++ FDPServerInterface.pfnReadVirtualMemory = &FDPVBOX_readVirtualMemory; ++ FDPServerInterface.pfnReadPhysicalMemory = &FDPVBOX_readPhysicalMemory; ++ FDPServerInterface.pfnSetBreakpoint = &FDPVBOX_setBreakpoint; ++ FDPServerInterface.pfnReadPhysicalMemory = &FDPVBOX_readPhysicalMemory; ++ FDPServerInterface.pfnSave = &FDPVBOX_Save; ++ FDPServerInterface.pfnRestore = &FDPVBOX_Restore; ++ FDPServerInterface.pfnReboot = &FDPVBOX_Reboot; ++ FDPServerInterface.pfnInjectInterrupt = &FDPVBOX_InjectInterrupt; ++ ++ if (FDP_SetFDPServer(pFDPServer, &FDPServerInterface) == false){ ++ printf("Failed to FDP_SerFDPServer\n"); ++ return NULL; ++ } ++ ++ printf("FDP_SetFDPServer OK\n"); ++ ++ VMR3SetFDPShm(pUVM, pFDPServer); ++ ++ printf("VMR3SetFDPShm OK\n"); ++ ++ if (FDP_ServerLoop(pFDPServer) == false){ ++ printf("Failed to FDP_ServerLoop\n"); ++ return NULL; ++ } ++ ++ if(pUserHandle != NULL){ ++ free(pUserHandle); ++ } ++ ++ return NULL; ++} ++/*ENDMYCODE*/ + + /** + * Checks if there is input. +@@ -215,6 +1127,10 @@ static DECLCALLBACK(int) dbgcTcpConnection(RTSOCKET Sock, void *pvUser) + */ + DBGDECL(int) DBGCTcpCreate(PUVM pUVM, void **ppvData) + { ++ /*MYCODE*/ ++ pthread_t t; ++ pthread_create(&t, NULL, FDPServerThread, pUVM); ++ /*ENDMYCODE*/ + /* + * Check what the configuration says. + */ +diff --git a/src/VBox/Devices/Audio/DevHDACommon.cpp b/src/VBox/Devices/Audio/DevHDACommon.cpp +index d32dcbc8..33af3353 100644 +--- a/src/VBox/Devices/Audio/DevHDACommon.cpp ++++ b/src/VBox/Devices/Audio/DevHDACommon.cpp +@@ -179,9 +179,9 @@ bool hdaR3WalClkSet(PHDASTATE pThis, uint64_t u64WalClk, bool fForce) + + const uint64_t u64WalClkNew = hdaWalClkGetCurrent(pThis); + +- Log3Func(("Cur: %RU64, New: %RU64 (force %RTbool) -> %RU64 %s\n", ++ /*Log3Func(("Cur: %RU64, New: %RU64 (force %RTbool) -> %RU64 %s\n", + u64WalClkCur, u64WalClk, fForce, +- u64WalClkNew, u64WalClkNew == u64WalClk ? "[OK]" : "[DELAYED]")); ++ u64WalClkNew, u64WalClkNew == u64WalClk ? "[OK]" : "[DELAYED]"));*/ + + return (u64WalClkNew == u64WalClk); + } +diff --git a/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp b/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp +index 3c809e84..63d33b07 100644 +--- a/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp ++++ b/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp +@@ -1029,10 +1029,10 @@ static int lsilogicR3ProcessMessageRequest(PLSILOGICSCSI pThis, PMptMessageHdr p + bool fForceReplyPostFifo = false; + + # ifdef LOG_ENABLED +- if (pMessageHdr->u8Function < RT_ELEMENTS(g_apszMPTFunctionNames)) ++/* if (pMessageHdr->u8Function < RT_ELEMENTS(g_apszMPTFunctionNames)) + Log(("Message request function: %s\n", g_apszMPTFunctionNames[pMessageHdr->u8Function])); + else +- Log(("Message request function: \n")); ++ Log(("Message request function: \n"));*/ + # endif + + memset(pReply, 0, sizeof(MptReplyUnion)); +diff --git a/src/VBox/VMM/VMMAll/HMAll.cpp b/src/VBox/VMM/VMMAll/HMAll.cpp +index 01243311..3da9d741 100644 +--- a/src/VBox/VMM/VMMAll/HMAll.cpp ++++ b/src/VBox/VMM/VMMAll/HMAll.cpp +@@ -487,6 +487,20 @@ static void hmPokeCpuForTlbFlush(PVMCPU pVCpu, bool fAccountFlushStat) + STAM_COUNTER_INC(&pVCpu->hm.s.StatFlushPageManual); + } + ++/*MYCODE*/ ++//Ensure that all CPU are paused ! ++VMM_INT_DECL(int) HMFlushTLBOnAllVCpus2(PVM pVM) ++{ ++ for (VMCPUID idCpu = 0; idCpu < pVM->cCpus; idCpu++){ ++ PVMCPU pVCpu = &pVM->aCpus[idCpu]; ++ HMFlushTlb(pVCpu); ++ VMCPU_FF_SET(pVCpu, VMCPU_FF_TLB_FLUSH); ++ hmPokeCpuForTlbFlush(pVCpu, true /* fAccountFlushStat */); ++ } ++ ++ return VINF_SUCCESS; ++} ++/*ENDMYCODE*/ + + /** + * Invalidates a guest page on all VCPUs. +diff --git a/src/VBox/VMM/VMMAll/PGMAll.cpp b/src/VBox/VMM/VMMAll/PGMAll.cpp +index 66240586..3aff7c1d 100644 +--- a/src/VBox/VMM/VMMAll/PGMAll.cpp ++++ b/src/VBox/VMM/VMMAll/PGMAll.cpp +@@ -1878,6 +1878,270 @@ static int pgmShwGetEPTPDPtr(PVMCPU pVCpu, RTGCPTR64 GCPtr, PEPTPDPT *ppPdpt, PE + return VINF_SUCCESS; + } + ++/*MYCODE*/ ++VMMDECL(int) PGMShwGetHCPage(PVMCPU pVCpu, uint64_t GCPhys, uint64_t *HCPhys) ++{ ++ PEPTPDPT ppPdpt; ++ PEPTPD pShwPD; ++ int rc; ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ pgmLock(pVM); ++ ++ const unsigned iPd = ((GCPhys >> SHW_PD_SHIFT) & SHW_PD_MASK); ++ rc = pgmShwGetEPTPDPtr(pVCpu, GCPhys, &ppPdpt, &pShwPD); ++ EPTPDE *Pde; ++ Pde = &pShwPD->a[iPd]; ++ ++ if(Pde->n.u1Size == 1) ++ { //2M ++ uint64_t test = *((uint64_t*)&Pde->b); ++ *HCPhys = (test & 0xFFFFFFFFFFE00000); ++ }else ++ { //4K ++ PSHWPT pPT; ++ rc = PGM_HCPHYS_2_PTR(pVM, pVCpu, Pde->u & SHW_PDE_PG_MASK, &pPT); ++ if (RT_SUCCESS(rc)) ++ { ++ const unsigned iPt = (GCPhys >> SHW_PT_SHIFT) & SHW_PT_MASK; ++ EPTPTE *Pte = (EPTPTE*)&pPT->a[iPt]; ++ ++ uint64_t test = *((uint64_t*)&Pte->n); ++ *HCPhys = (test & 0xFFFFFFFFFFFFF000); ++ } ++ } ++ pgmUnlock(pVM); ++ return rc; ++} ++ ++void logRelPDE(EPTPDE *Pde) ++{ ++ LogRel(("Pde->b.u1Present %p\n", Pde->b.u1Present)); ++ LogRel(("Pde->b.u1Write %p\n", Pde->b.u1Write)); ++ LogRel(("Pde->b.u1Execute %p\n", Pde->b.u1Execute)); ++ LogRel(("Pde->b.u3EMT %p\n", Pde->b.u3EMT)); ++ LogRel(("Pde->b.u1IgnorePAT %p\n", Pde->b.u1IgnorePAT)); ++ LogRel(("Pde->b.u1Size %p\n", Pde->b.u1Size)); ++ LogRel(("Pde->b.u4Available %p\n", Pde->b.u4Available)); ++ LogRel(("Pde->b.u9Reserved %p\n", Pde->b.u9Reserved)); ++ LogRel(("Pde->b.u31PhysAddr %p\n", Pde->b.u31PhysAddr)); ++ LogRel(("Pde->b.u12Available %p\n", Pde->b.u12Available)); ++} ++ ++void logRelPTE(EPTPTE *Pte) ++{ ++ LogRel(("------------------------------------\n")); ++ LogRel(("Pte->n.u1Present %p\n", Pte->n.u1Present)); ++ LogRel(("Pte->n.u1Write %p\n", Pte->n.u1Write)); ++ LogRel(("Pte->n.u1Execute %p\n", Pte->n.u1Execute)); ++ LogRel(("------------------------------------\n")); ++} ++ ++VMMDECL(int) PGMShwSetHCPage(PVMCPU pVCpu, uint64_t GCPhys, uint64_t HCPhys) ++{ ++ PEPTPDPT ppPdpt; ++ PEPTPD pShwPD; ++ int rc; ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ pgmLock(pVM); ++ ++ const unsigned iPd = ((GCPhys >> SHW_PD_SHIFT) & SHW_PD_MASK); ++ rc = pgmShwGetEPTPDPtr(pVCpu, GCPhys, &ppPdpt, &pShwPD); ++ EPTPDE *Pde; ++ Pde = &pShwPD->a[iPd]; ++ ++ if(Pde->n.u1Size == 1){ //2M ++ Pde->au64[0] = (Pde->au64[0] & 0x1FFFFF) | (HCPhys & 0xFFFFFFFFFFE00000); ++ }else{ //4K ++ PSHWPT pPT; ++ rc = PGM_HCPHYS_2_PTR(pVM, pVCpu, Pde->u & SHW_PDE_PG_MASK, &pPT); ++ if (RT_SUCCESS(rc)){ ++ const unsigned iPt = (GCPhys >> SHW_PT_SHIFT) & SHW_PT_MASK; ++ EPTPTE *Pte = (EPTPTE*)&pPT->a[iPt]; ++ ++ Pte->au64[0] = (Pte->au64[0] & 0xFFF) | (HCPhys & 0xFFFFFFFFFFFFF000); ++ } ++ } ++ ++ pgmUnlock(pVM); ++ return rc; ++} ++ ++VMMDECL(int) PGMShwChangeFlags(PVMCPU pVCpu, uint64_t GCPhys, uint8_t orPresent, uint8_t andPresent, uint8_t orWrite, uint8_t andWrite, uint8_t orExecute, uint8_t andExecute) ++{ ++ PEPTPDPT ppPdpt; ++ PEPTPD pShwPD; ++ int rc; ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ pgmLock(pVM); ++ ++ const unsigned iPd = ((GCPhys >> SHW_PD_SHIFT) & SHW_PD_MASK); ++ rc = pgmShwGetEPTPDPtr(pVCpu, GCPhys, &ppPdpt, &pShwPD); ++ EPTPDE *Pde; ++ Pde = &pShwPD->a[iPd]; ++ ++ if(Pde->n.u1Size == 1){ //2M ++ Pde->b.u1Present = (Pde->b.u1Present | orPresent) & andPresent; ++ Pde->b.u1Write = (Pde->b.u1Write | orWrite) & andWrite; ++ Pde->b.u1Execute = (Pde->b.u1Execute | orExecute) & andExecute; ++ }else{ //4K ++ PSHWPT pPT; ++ rc = PGM_HCPHYS_2_PTR(pVM, pVCpu, Pde->u & SHW_PDE_PG_MASK, &pPT); ++ if (RT_SUCCESS(rc)){ ++ const unsigned iPt = (GCPhys >> SHW_PT_SHIFT) & SHW_PT_MASK; ++ EPTPTE *Pte = (EPTPTE*)&pPT->a[iPt]; ++ ++ Pte->n.u1Present = (Pte->n.u1Present | orPresent) & andPresent; ++ Pte->n.u1Write = (Pte->n.u1Write | orWrite) & andWrite; ++ Pte->n.u1Execute = (Pte->n.u1Execute | orExecute) & andExecute; ++ } ++ } ++ ++ pgmUnlock(pVM); ++ return rc; ++} ++ ++#define MEMORY_SIZE 0x80000000 ++ ++VMMDECL(int) PGMShwSaveRights(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ PEPTPDPT ppPdpt; ++ PEPTPD pShwPD; ++ int returnFlags = 0; ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ pgmLock(pVM); ++ ++ const unsigned iPd = ((GCPhys >> SHW_PD_SHIFT) & SHW_PD_MASK); ++ int rc = pgmShwGetEPTPDPtr(pVCpu, GCPhys, &ppPdpt, &pShwPD); ++ EPTPDE *Pde; ++ Pde = &pShwPD->a[iPd]; ++ ++ if(Pde->n.u1Size == 1){ //2M ++ //TODO !!!! ++ }else{ //4K ++ PSHWPT pPT; ++ rc = PGM_HCPHYS_2_PTR(pVM, pVCpu, Pde->u & SHW_PDE_PG_MASK, &pPT); ++ if (RT_SUCCESS(rc)){ ++ const unsigned iPt = (GCPhys >> SHW_PT_SHIFT) & SHW_PT_MASK; ++ EPTPTE *pPte = (EPTPTE*)&pPT->a[iPt]; ++ ++ uint32_t PfnIndex = ((GCPhys & X86_PAGE_4K_BASE_MASK) >> X86_PAGE_4K_SHIFT); ++ ++ PfnEntrie_t* pTmpPfnEntrie; ++#ifdef IN_RING0 ++ pTmpPfnEntrie = pVM->mystate.s.pPfnTableR0; ++#else ++ pTmpPfnEntrie = pVM->mystate.s.pPfnTableR3; ++#endif ++ pTmpPfnEntrie[PfnIndex].u.u1Present = pPte->n.u1Present; ++ pTmpPfnEntrie[PfnIndex].u.u1Write = pPte->n.u1Write; ++ pTmpPfnEntrie[PfnIndex].u.u1Execute = pPte->n.u1Execute; ++ ++ //logRelPTE(Pte); ++ } ++ } ++ ++ pgmUnlock(pVM); ++ return rc; ++} ++ ++VMMDECL(int) PGMShwRestoreRights(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ if(GCPhys < MEMORY_SIZE) ++ { ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ uint32_t PfnIndex = ((GCPhys & X86_PAGE_4K_BASE_MASK) >> X86_PAGE_4K_SHIFT); ++ ++ ++ PfnEntrie_t* tmpPfnEntrie; ++#ifdef IN_RING0 ++ tmpPfnEntrie = pVM->mystate.s.pPfnTableR0; ++#else ++ tmpPfnEntrie = pVM->mystate.s.pPfnTableR3; ++#endif ++ PGMShwChangeFlags(pVCpu, GCPhys, ++ tmpPfnEntrie[PfnIndex].u.u1Present,tmpPfnEntrie[PfnIndex].u.u1Present, ++ tmpPfnEntrie[PfnIndex].u.u1Write, tmpPfnEntrie[PfnIndex].u.u1Write, ++ tmpPfnEntrie[PfnIndex].u.u1Execute, tmpPfnEntrie[PfnIndex].u.u1Execute); ++ } ++ ++ return VINF_SUCCESS; ++} ++ ++VMMDECL(int) PGMShwSetBreakable(PVMCPU pVCpu, uint64_t GCPhys, bool Breakable) ++{ ++ if(GCPhys < MEMORY_SIZE){ ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ uint32_t PfnIndex = ((GCPhys & X86_PAGE_4K_BASE_MASK) >> X86_PAGE_4K_SHIFT); ++ ++ PfnEntrie_t* tmpPfnEntrie; ++#ifdef IN_RING0 ++ tmpPfnEntrie = pVM->mystate.s.pPfnTableR0; ++#else ++ tmpPfnEntrie = pVM->mystate.s.pPfnTableR3; ++#endif ++ tmpPfnEntrie[PfnIndex].u.u1Breakable = Breakable; ++ //LogRel(("PGMShwSetBreakable %p PfnTable[%d].u1Breakable %s\n", GCPhys, PfnIndex, Breakable ? "true" : "false")); ++ } ++ return VINF_SUCCESS; ++} ++ ++ ++VMMDECL(bool) PGMShwIsBreakable(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ if(GCPhys < MEMORY_SIZE){ ++ uint32_t PfnIndex = ((GCPhys & X86_PAGE_4K_BASE_MASK) >> X86_PAGE_4K_SHIFT); ++ PfnEntrie_t* tmpPfnEntrie; ++#ifdef IN_RING0 ++ tmpPfnEntrie = pVM->mystate.s.pPfnTableR0; ++#else ++ tmpPfnEntrie = pVM->mystate.s.pPfnTableR3; ++#endif ++ //LogRel(("PGMShwIsBreakable %p PfnTable[%d].u1Breakable %s\n", GCPhys, PfnIndex, tmpPfnEntrie[PfnIndex].u.u1Breakable ? "true" : "false")); ++ return tmpPfnEntrie[PfnIndex].u.u1Breakable; ++ } ++ return false; ++} ++ ++VMMDECL(int) PGMShwNoPresent(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ return PGMShwChangeFlags(pVCpu, GCPhys, 0, 0, 0, 1, 0, 1); ++} ++ ++VMMDECL(int) PGMShwPresent(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ return PGMShwChangeFlags(pVCpu, GCPhys, 1, 1, 0, 1, 0, 1); ++} ++ ++VMMDECL(int) PGMShwNoWrite(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ return PGMShwChangeFlags(pVCpu, GCPhys, 0, 1, 0, 0, 0, 1); ++} ++ ++VMMDECL(int) PGMShwWrite(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ return PGMShwChangeFlags(pVCpu, GCPhys, 0, 1, 1, 1, 0, 1); ++} ++ ++VMMDECL(int) PGMShwNoExecute(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ return PGMShwChangeFlags(pVCpu, GCPhys, 0, 1, 0, 1, 0, 0); ++} ++ ++VMMDECL(int) PGMShwExecute(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ return PGMShwChangeFlags(pVCpu, GCPhys, 0, 1, 0, 1, 1, 1); ++} ++ ++VMMDECL(int) PGMShwInvalidate(PVMCPU pVCpu, uint64_t GCPhys) ++{ ++#ifndef IN_RING0 ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ HMFlushTLBOnAllVCpus2(pVM); ++#endif ++ return VINF_SUCCESS; ++} ++/*ENDMYCODE*/ + #endif /* IN_RC */ + + #ifdef IN_RING0 +diff --git a/src/VBox/VMM/VMMAll/VMMAll.cpp b/src/VBox/VMM/VMMAll/VMMAll.cpp +index c5baec0e..8a348ed5 100644 +--- a/src/VBox/VMM/VMMAll/VMMAll.cpp ++++ b/src/VBox/VMM/VMMAll/VMMAll.cpp +@@ -395,3 +395,48 @@ uint32_t vmmGetBuildType(void) + return uRet; + } + ++/*MYCODE*/ ++VMM_INT_DECL(bool) VMMMatchBreakpointId(PVM pVM, int BreakpointId, RTGCPHYS GCPhys, uint8_t BreakpointType, int BreakpointAccess) ++{ ++ if(BreakpointId >= 0 ++ || BreakpointId < MAX_BREAKPOINT_ID){ ++ BreakpointEntrie_t *TempBreakpointEntrie = &pVM->bp.l[BreakpointId]; ++ if(TempBreakpointEntrie->breakpointActivated ++ && TempBreakpointEntrie->breakpointType == BreakpointType ++ && (TempBreakpointEntrie->breakpointAccessType & BreakpointAccess)){ ++ for(int j=0; jbreakpointGCPhysAreaCount; j++){ ++ if(GCPhys >= TempBreakpointEntrie->breakpointGCPhysAreaTable[j].Start ++ && GCPhys < TempBreakpointEntrie->breakpointGCPhysAreaTable[j].End){ ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++VMM_INT_DECL(int) VMMGetBreakpointId(PVM pVM, RTGCPHYS GCPhys, uint8_t BreakpointType, int BreakpointAccess) ++{ ++ for(int i=0; ibp.l[i].breakpointActivated ++ && pVM->bp.l[i].breakpointType == BreakpointType){ ++ for(int j=0; jbp.l[i].breakpointGCPhysAreaCount; j++){ ++ if((GCPhys & ~(_4K-1)) == (pVM->bp.l[i].breakpointGCPhysAreaTable[j].Start & ~(_4K-1))){ ++ return i; ++ } ++ } ++ } ++ } ++ return -1; ++} ++/*ENDMYCODE*/ +\ No newline at end of file +diff --git a/src/VBox/VMM/VMMR0/GVMMR0.cpp b/src/VBox/VMM/VMMR0/GVMMR0.cpp +index 843855d9..2cda9824 100644 +--- a/src/VBox/VMM/VMMR0/GVMMR0.cpp ++++ b/src/VBox/VMM/VMMR0/GVMMR0.cpp +@@ -924,6 +924,23 @@ GVMMR0DECL(int) GVMMR0CreateVM(PSUPDRVSESSION pSession, uint32_t cCpus, PVM *ppV + AssertCompileMemberAlignment(VM, tm, 64); + AssertCompileMemberAlignment(VM, aCpus, PAGE_SIZE); + ++ /*MYCODE*/ ++ RTR0MEMOBJ PfnTableMemObj; ++ rc = RTR0MemObjAllocPage(&PfnTableMemObj, sizeof(PfnEntrie_t)*512*1024, false /* fExecutable */); ++ if (RT_SUCCESS(rc)){ ++ pVM->mystate.s.pPfnTableR0 = (PfnEntrie_t*)RTR0MemObjAddress(PfnTableMemObj); ++ RTR0MEMOBJ PfnTableMapObj; ++ rc = RTR0MemObjMapUser(&PfnTableMapObj, PfnTableMemObj, (RTR3PTR)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE, NIL_RTR0PROCESS); ++ if (RT_SUCCESS(rc)) { ++ pVM->mystate.s.pPfnTableR3 = (PfnEntrie_t*)RTR0MemObjAddressR3(PfnTableMapObj); ++ }else{ ++ return -1; ++ } ++ }else{ ++ return -1; ++ } ++ /*ENDMYCODE*/ ++ + rc = RTR0MemObjAllocPage(&pGVM->gvmm.s.VMPagesMemObj, cPages * sizeof(SUPPAGE), false /* fExecutable */); + if (RT_SUCCESS(rc)) + { +diff --git a/src/VBox/VMM/VMMR0/HMVMXR0.cpp b/src/VBox/VMM/VMMR0/HMVMXR0.cpp +index e6233c5a..c32e0e93 100644 +--- a/src/VBox/VMM/VMMR0/HMVMXR0.cpp ++++ b/src/VBox/VMM/VMMR0/HMVMXR0.cpp +@@ -43,6 +43,43 @@ + #include "HMVMXR0.h" + #include "dtrace/VBoxVMM.h" + ++/*MYCODE*/ ++#include ++#include ++#include ++ ++//TODO: Move this in VMMALL as there is a copy in VMMR3 ++void UpdateFdpCpuCtx(PVMCPU pVCpu) ++{ ++ return; //TODO ++ ++ PCCPUMCTXCORE pCtxCore = CPUMGetGuestCtxCore(pVCpu); ++ FDP_CPU_CTX* pFdpCpuCtx = (FDP_CPU_CTX *)pVCpu->mystate.s.pCpuShm; ++ ++ pFdpCpuCtx->rip = pCtxCore->rip; ++ pFdpCpuCtx->rax = pCtxCore->rax; ++ pFdpCpuCtx->rcx = pCtxCore->rcx; ++ pFdpCpuCtx->rdx = pCtxCore->rdx; ++ pFdpCpuCtx->rbx = pCtxCore->rbx; ++ pFdpCpuCtx->rsp = pCtxCore->rsp; ++ pFdpCpuCtx->rbp = pCtxCore->rbp; ++ pFdpCpuCtx->rsi = pCtxCore->rsi; ++ pFdpCpuCtx->rdi = pCtxCore->rdi; ++ pFdpCpuCtx->r8 = pCtxCore->r8; ++ pFdpCpuCtx->r9 = pCtxCore->r9; ++ pFdpCpuCtx->r10 = pCtxCore->r10; ++ pFdpCpuCtx->r11 = pCtxCore->r11; ++ pFdpCpuCtx->r12 = pCtxCore->r12; ++ pFdpCpuCtx->r13 = pCtxCore->r13; ++ pFdpCpuCtx->r14 = pCtxCore->r14; ++ pFdpCpuCtx->r15 = pCtxCore->r15; ++ pFdpCpuCtx->cr0 = CPUMGetGuestCR0(pVCpu); ++ pFdpCpuCtx->cr2 = CPUMGetGuestCR2(pVCpu); ++ pFdpCpuCtx->cr3 = CPUMGetGuestCR3(pVCpu); ++ pFdpCpuCtx->cr4 = CPUMGetGuestCR4(pVCpu); ++} ++/*ENDMYCODE*/ ++ + #ifdef DEBUG_ramshankar + # define HMVMX_ALWAYS_SAVE_GUEST_RFLAGS + # define HMVMX_ALWAYS_SAVE_FULL_GUEST_STATE +@@ -54,6 +89,10 @@ + # define HMVMX_ALWAYS_SWAP_EFER + #endif + ++/*MYCODE*/ ++#define HMVMX_ALWAYS_TRAP_ALL_XCPTS ++/*ENDMYCODE*/ ++ + + /********************************************************************************************************************************* + * Defined Constants And Macros * +@@ -973,6 +1012,11 @@ static void hmR0VmxStructsFree(PVM pVM) + */ + static int hmR0VmxStructsAlloc(PVM pVM) + { ++ /*MYCODE*/ ++ pVM->mystate.s.PageSpinlock = NIL_RTSPINLOCK; ++ RTSpinlockCreate(&pVM->mystate.s.PageSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, pVM->mystate.s.PageSpinLockName); ++ /*ENDMYCODE*/ ++ + /* + * Initialize members up-front so we can cleanup properly on allocation failure. + */ +@@ -4067,8 +4111,10 @@ static int hmR0VmxExportSharedDebugState(PVMCPU pVCpu) + if (pVCpu->hm.s.vmx.Ctls.u32EntryCtls & VMX_ENTRY_CTLS_LOAD_DEBUG) + { + /* Validate. Intel spec. 17.2 "Debug Registers", recompiler paranoia checks. */ +- Assert((pVCpu->cpum.GstCtx.dr[7] & (X86_DR7_MBZ_MASK | X86_DR7_RAZ_MASK)) == 0); +- Assert((pVCpu->cpum.GstCtx.dr[7] & X86_DR7_RA1_MASK) == X86_DR7_RA1_MASK); ++ /*MYCODE*/ ++ //Assert((pVCpu->cpum.GstCtx.dr[7] & (X86_DR7_MBZ_MASK | X86_DR7_RAZ_MASK)) == 0); ++ //Assert((pVCpu->cpum.GstCtx.dr[7] & X86_DR7_RA1_MASK) == X86_DR7_RA1_MASK); ++ /*ENDMYCODE*/ + } + #endif + +@@ -4175,11 +4221,25 @@ static int hmR0VmxExportSharedDebugState(PVMCPU pVCpu) + pVCpu->hm.s.fUsingHyperDR7 = false; + } + ++ /*MYCODE*/ ++ //Always intercept MovDRx ++ fInterceptMovDRx = true; ++ /*ENDMYCODE*/ ++ + if (fInterceptMovDRx) + uProcCtls |= VMX_PROC_CTLS_MOV_DR_EXIT; + else + uProcCtls &= ~VMX_PROC_CTLS_MOV_DR_EXIT; + ++ /*MYCODE*/ ++ //Always Intercept MovDrx ++ uProcCtls |= VMX_PROC_CTLS_MOV_DR_EXIT; ++ uProcCtls |= VMX_PROC_CTLS_CR3_LOAD_EXIT; ++ uProcCtls |= VMX_PROC_CTLS_CR3_STORE_EXIT; ++ uProcCtls |= VMX_PROC_CTLS_CR8_LOAD_EXIT; ++ uProcCtls |= VMX_PROC_CTLS_CR8_STORE_EXIT; ++ /*ENDCODE*/ ++ + /* + * Update the processor-based VM-execution controls with the MOV-DRx intercepts and the + * monitor-trap flag and update our cache. +@@ -8918,6 +8978,9 @@ static void hmR0VmxPostRunGuest(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient, int r + rc = hmR0VmxImportGuestState(pVCpu, CPUMCTX_EXTRN_HM_VMX_INT_STATE); + AssertRC(rc); + #endif ++ /*MYCODE*/ ++ UpdateFdpCpuCtx(pVCpu); ++ /*ENDMYCODE*/ + + /* + * Sync the TPR shadow with our APIC state. +@@ -10142,6 +10205,11 @@ static VBOXSTRICTRC hmR0VmxRunGuestCodeDebug(PVMCPU pVCpu) + STAM_PROFILE_ADV_STOP(&pVCpu->hm.s.StatExitHandling, x); + if (rcStrict != VINF_SUCCESS) + break; ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired){ ++ break; ++ } ++ /*ENDMYCODE*/ + if (cLoops > pVCpu->CTX_SUFF(pVM)->hm.s.cMaxResumeLoops) + { + STAM_COUNTER_INC(&pVCpu->hm.s.StatSwitchMaxResumeLoops); +@@ -12053,6 +12121,19 @@ HMVMX_EXIT_DECL hmR0VmxExitRdmsr(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient) + } + #endif + ++ /*MYCODE*/ ++ for(int iBreakpointId=0; iBreakpointIdCTX_SUFF(pVM)->bp.l[iBreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_MSRHBP ++ && pTempBreakpointEntrie->breakpointAccessType == FDP_READ_BP ++ && (pTempBreakpointEntrie->breakpointGCPtr == idMsr || pTempBreakpointEntrie->breakpointGCPtr == 0)){ ++ pVCpu->mystate.s.bMsrHyperBreakPointHitted = true; ++ return VINF_EM_HALT; ++ } ++ } ++ /*ENDMYCODE*/ ++ + VBOXSTRICTRC rcStrict = IEMExecDecodedRdmsr(pVCpu, pVmxTransient->cbInstr); + STAM_COUNTER_INC(&pVCpu->hm.s.StatExitRdmsr); + if (rcStrict == VINF_SUCCESS) +@@ -12101,6 +12182,19 @@ HMVMX_EXIT_DECL hmR0VmxExitWrmsr(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient) + + Log4Func(("ecx=%#RX32 edx:eax=%#RX32:%#RX32\n", idMsr, pVCpu->cpum.GstCtx.edx, pVCpu->cpum.GstCtx.eax)); + ++ /*MYCODE*/ ++ for(int iBreakpointId=0; iBreakpointIdCTX_SUFF(pVM)->bp.l[iBreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_MSRHBP ++ && pTempBreakpointEntrie->breakpointAccessType == FDP_WRITE_BP ++ && (pTempBreakpointEntrie->breakpointGCPtr == idMsr || pTempBreakpointEntrie->breakpointGCPtr == 0)){ ++ pVCpu->mystate.s.bMsrHyperBreakPointHitted = true; ++ return VINF_EM_HALT; ++ } ++ } ++ /*ENDMYCODE*/ ++ + VBOXSTRICTRC rcStrict = IEMExecDecodedWrmsr(pVCpu, pVmxTransient->cbInstr); + STAM_COUNTER_INC(&pVCpu->hm.s.StatExitWrmsr); + +@@ -12266,6 +12360,9 @@ HMVMX_EXIT_DECL hmR0VmxExitMovCRx(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient) + PVM pVM = pVCpu->CTX_SUFF(pVM); + RTGCUINTPTR const uExitQual = pVmxTransient->uExitQual; + uint32_t const uAccessType = VMX_EXIT_QUAL_CRX_ACCESS(uExitQual); ++ /*MYCODE*/ ++ bool bBreakpointHitted = false; ++ /*ENDMYCODE*/ + switch (uAccessType) + { + case VMX_EXIT_QUAL_CRX_ACCESS_WRITE: /* MOV to CRx */ +@@ -12350,6 +12447,19 @@ HMVMX_EXIT_DECL hmR0VmxExitMovCRx(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient) + AssertMsgFailed(("Invalid CRx register %#x\n", VMX_EXIT_QUAL_CRX_REGISTER(uExitQual))); + break; + } ++ /*MYCODE*/ ++ //Looking for a matching breakpoint ++ for(int iBreakpointId=0; iBreakpointIdCTX_SUFF(pVM)->bp.l[iBreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_CRHBP ++ && pTempBreakpointEntrie->breakpointAccessType == FDP_WRITE_BP ++ && (pTempBreakpointEntrie->breakpointGCPtr == VMX_EXIT_QUAL_CRX_REGISTER(uExitQual))){ ++ bBreakpointHitted = true; ++ break; ++ } ++ } ++ /*ENDMYCODE*/ + break; + } + +@@ -12429,6 +12539,12 @@ HMVMX_EXIT_DECL hmR0VmxExitMovCRx(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient) + } + + STAM_PROFILE_ADV_STOP(&pVCpu->hm.s.StatExitMovCRx, y2); ++ /*MYCODE*/ ++ if(bBreakpointHitted == true){ ++ pVCpu->mystate.s.bCrHyperBreakPointHitted = true; ++ return VINF_EM_HALT; ++ } ++ /*ENDMYCODE*/ + NOREF(pVM); + return rcStrict; + } +@@ -12824,6 +12940,99 @@ HMVMX_EXIT_DECL hmR0VmxExitApicAccess(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient) + */ + HMVMX_EXIT_DECL hmR0VmxExitMovDRx(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient) + { ++ /*MYCODE*/ ++ { ++ int rc2; ++ PCPUMCTX pMixedCtx = &pVCpu->cpum.GstCtx; ++ rc2 = hmR0VmxReadExitQualVmcs(pVCpu, pVmxTransient); ++ //rc2 |= hmR0VmxSaveGuestSegmentRegs(pVCpu, pMixedCtx); ++ rc2 |= HMVMX_CPUMCTX_IMPORT_STATE(pVCpu, CPUMCTX_EXTRN_SREG_MASK | CPUMCTX_EXTRN_DR7); ++ //rc2 |= hmR0VmxSaveGuestDR7(pVCpu, pMixedCtx); ++ AssertRCReturn(rc2, rc2); ++ ++ bool DRxWrite = false; ++ //if (VMX_EXIT_QUALIFICATION_DRX_DIRECTION(pVmxTransient->uExitQual) == VMX_EXIT_QUALIFICATION_DRX_DIRECTION_WRITE){ ++ if (VMX_EXIT_QUAL_DRX_DIRECTION(pVmxTransient->uExitQual) == VMX_EXIT_QUAL_DRX_DIRECTION_WRITE){ ++ DRxWrite = true; ++ } ++ ++ PVM pVM2 = pVCpu->CTX_SUFF(pVM); ++ ++ //Save the fakeDR to compare after the instruction ++ uint64_t aOldVisibleDr[8]; ++ aOldVisibleDr[0] = pVCpu->mystate.s.aGuestDr[0]; ++ aOldVisibleDr[1] = pVCpu->mystate.s.aGuestDr[1]; ++ aOldVisibleDr[2] = pVCpu->mystate.s.aGuestDr[2]; ++ aOldVisibleDr[3] = pVCpu->mystate.s.aGuestDr[3]; ++ aOldVisibleDr[7] = pVCpu->mystate.s.aGuestDr[7]; ++ ++ //TODO: not needed, we need to save the value when FDP_WriteRegister() ++ //Save Invisble Debug Register values used by HardHyperBreakpoint ++ uint64_t uInvisibleDr0 = ASMGetDR0(); ++ uint64_t uInvisibleDr1 = ASMGetDR1(); ++ uint64_t uInvisibleDr2 = ASMGetDR2(); ++ uint64_t uInvisibleDr3 = ASMGetDR3(); ++ uint64_t uInvisibleDr6 = ASMGetDR6(); ++ uint64_t uInvisibleDr7 = CPUMGetGuestDR7(pVCpu); ++ ++ //Load fake DR Values ++ CPUMSetHyperDR0(pVCpu, pVCpu->mystate.s.aGuestDr[0]); ++ CPUMSetHyperDR1(pVCpu, pVCpu->mystate.s.aGuestDr[1]); ++ CPUMSetHyperDR2(pVCpu, pVCpu->mystate.s.aGuestDr[2]); ++ CPUMSetHyperDR3(pVCpu, pVCpu->mystate.s.aGuestDr[3]); ++ CPUMSetHyperDR6(pVCpu, pVCpu->mystate.s.aGuestDr[6]); ++ VMXWriteVmcs32(VMX_VMCS_GUEST_DR7, (uint32_t)pVCpu->mystate.s.aGuestDr[7]); ++ ++ //Disable #MovDRx ++ pVCpu->hm.s.vmx.Ctls.u32ProcCtls &= ~VMX_PROC_CTLS_MOV_DR_EXIT; ++ //Enable MTF ++ pVCpu->hm.s.vmx.Ctls.u32ProcCtls |= VMX_PROC_CTLS_MONITOR_TRAP_FLAG; ++ rc2 = VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.Ctls.u32ProcCtls); ++ ++ //Single Step ++ //hmR0VmxRunGuestCodeNormal(pVM2, pVCpu, pMixedCtx); ++ hmR0VmxRunGuestCodeNormal(pVCpu); ++ rc2 = hmR0VmxReadExitQualVmcs(pVCpu, pVmxTransient); ++ //rc2 |= hmR0VmxSaveGuestSegmentRegs(pVCpu, pMixedCtx); ++ //rc2 |= hmR0VmxSaveGuestDR7(pVCpu, pMixedCtx); ++ rc2 |= HMVMX_CPUMCTX_IMPORT_STATE(pVCpu, CPUMCTX_EXTRN_SREG_MASK | CPUMCTX_EXTRN_DR7); ++ ++ //Disable MTF ++ pVCpu->hm.s.vmx.Ctls.u32ProcCtls &= ~VMX_PROC_CTLS_MONITOR_TRAP_FLAG; ++ //Enable #MovDrx ++ pVCpu->hm.s.vmx.Ctls.u32ProcCtls |= VMX_PROC_CTLS_MOV_DR_EXIT; ++ rc2 = VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.Ctls.u32ProcCtls); ++ ++ //Save new Visible Debug Register values (Usefull only on write) ++ pVCpu->mystate.s.aGuestDr[0] = ASMGetDR0(); ++ pVCpu->mystate.s.aGuestDr[1] = ASMGetDR1(); ++ pVCpu->mystate.s.aGuestDr[2] = ASMGetDR2(); ++ pVCpu->mystate.s.aGuestDr[3] = ASMGetDR3(); ++ pVCpu->mystate.s.aGuestDr[6] = ASMGetDR6(); ++ pVCpu->mystate.s.aGuestDr[7] = pMixedCtx->dr[7]; ++ ++ //Restore Invisible Debug Register values ++ CPUMSetHyperDR0(pVCpu, uInvisibleDr0); ++ CPUMSetHyperDR1(pVCpu, uInvisibleDr1); ++ CPUMSetHyperDR2(pVCpu, uInvisibleDr2); ++ CPUMSetHyperDR3(pVCpu, uInvisibleDr3); ++ CPUMSetHyperDR6(pVCpu, uInvisibleDr6); ++ CPUMSetGuestDR7(pVCpu, (uint32_t)uInvisibleDr7); ++ VMXWriteVmcs32(VMX_VMCS_GUEST_DR7, (uint32_t)uInvisibleDr7); ++ ++ //If a Visible Debug Register changed go to ring-3 install/remove a breakpoint ++ if (aOldVisibleDr[0] != pVCpu->mystate.s.aGuestDr[0] ++ || aOldVisibleDr[1] != pVCpu->mystate.s.aGuestDr[1] ++ || aOldVisibleDr[2] != pVCpu->mystate.s.aGuestDr[2] ++ || aOldVisibleDr[3] != pVCpu->mystate.s.aGuestDr[3] ++ || aOldVisibleDr[7] != pVCpu->mystate.s.aGuestDr[7]){ ++ pVCpu->mystate.s.bInstallDrBreakpointRequired = true; ++ return VINF_EM_HALT; ++ } ++ //Go ! ++ return VINF_SUCCESS; ++ } ++ /*ENDMYCODE*/ + HMVMX_VALIDATE_EXIT_HANDLER_PARAMS(pVCpu, pVmxTransient); + + /* We should -not- get this VM-exit if the guest's debug registers were active. */ +@@ -13045,6 +13254,181 @@ HMVMX_EXIT_DECL hmR0VmxExitEptViolation(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransien + VBOXSTRICTRC rcStrict2 = PGMR0Trap0eHandlerNestedPaging(pVM, pVCpu, PGMMODE_EPT, uErrorCode, CPUMCTX2CORE(pCtx), GCPhys); + TRPMResetTrap(pVCpu); + ++ /*MYCODE*/ ++ if(PGMShwIsBreakable(pVCpu, GCPhys) == true){ ++ //PHMGLOBALCPUINFO pCpu = hmR0GetCurrentCpu(); ++ PCHMPHYSCPU pCpu = hmR0GetCurrentCpu(); ++ STAM_COUNTER_INC(&pVCpu->hm.s.StatExitGuestDE); ++ if(VMMGetBreakpointIdFromPage(pVM, GCPhys, FDP_PAGEHBP) >= 0){ //FDP_PAGEHBP ++ /*HMCPU_CF_SET(pVCpu, HM_CHANGED_GUEST_RIP ++ | HM_CHANGED_GUEST_RSP ++ | HM_CHANGED_GUEST_RFLAGS);*/ ++ ASMAtomicUoOrU64(&pVCpu->hm.s.fCtxChanged, HM_CHANGED_GUEST_RIP | HM_CHANGED_GUEST_RSP | HM_CHANGED_GUEST_RFLAGS); ++ ++ ++ int tmpAccess = 0x00; ++ if(pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_DATA_READ) ++ tmpAccess |= (int)FDP_READ_BP; ++ if(pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_DATA_WRITE) ++ tmpAccess |= (int)FDP_WRITE_BP; ++ if(pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_INSTR_FETCH) ++ tmpAccess |= (int)FDP_EXECUTE_BP; ++ ++ ++ STAM_COUNTER_INC(&pVCpu->hm.s.StatExitGuestDE); ++ //If it is one of our breakpoints, go to VMMR3 ! ++ int PageBreakpointId = VMMGetBreakpointId(pVM, GCPhys, FDP_PAGEHBP, tmpAccess); ++ if(PageBreakpointId >= (int)(4*pVM->cCpus)){ ++ //This is a host page breakpoint ! ++ pVCpu->mystate.s.bPageHyperBreakPointHitted = true; ++ ++ //RTSpinlockAcquire(pVM->mystate.s.PageSpinlock); ++ PGMShwRestoreRights(pVCpu, GCPhys); ++ //VMXR0InvalidatePhysPage(pVM, pVCpu, GCPhys); ++ hmR0VmxFlushEpt(pVCpu, pVM->hm.s.vmx.enmTlbFlushEpt); ++ //RTSpinlockRelease(pVM->mystate.s.PageSpinlock); ++ ++ return VINF_EM_HALT; ++ } ++ ++ if(PageBreakpointId >= 0 ++ && PageBreakpointId < (int)(4*pVM->cCpus)){ ++ STAM_COUNTER_INC(&pVCpu->hm.s.StatExitGuestGP); ++ //This is a Guest Hardware Breakpoint ! ++ //Update the guest dr6 ++ pCtx->dr[6] = pVCpu->mystate.s.aGuestDr[6]; ++ for(int i=0; i<4; i++){ ++ if(VMMMatchBreakpointId(pVM, i, GCPhys, FDP_PAGEHBP, tmpAccess)){ ++ pCtx->dr[6] = pCtx->dr[6] | ((uint64_t)(0x1 << (i))); ++ } ++ } ++ ASMSetDR6(pCtx->dr[6]); ++ ++ //Inject a INT1 into the guest ++ hmR0VmxSetPendingXcptDB(pVCpu); ++ return VINF_SUCCESS; ++ } ++ ++ //If it not the breakpoint then continue ! ++ //RTSpinlockAcquire(pVM->mystate.s.PageSpinlock); ++ PGMShwPresent(pVCpu, GCPhys); ++ PGMShwWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); ++ //Flush TLB ++//#ifdef IN_RING0 ++ PHMPHYSCPU pHostCpu = hmR0GetCurrentCpu(); ++ hmR0VmxFlushTaggedTlb(pHostCpu, pVCpu); ++//#endif ++ ++ //Active MTF ++ pVCpu->hm.s.vmx.Ctls.u32ProcCtls |= VMX_PROC_CTLS_MONITOR_TRAP_FLAG; ++ int rc3 = VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.Ctls.u32ProcCtls); ++ ++ //Single Step ++ //hmR0VmxRunGuestCodeNormal(pVM, pVCpu, pMixedCtx); ++ hmR0VmxRunGuestCodeNormal(pVCpu); ++ ++ //Disable MTF ++ pVCpu->hm.s.vmx.Ctls.u32ProcCtls &= ~VMX_PROC_CTLS_MONITOR_TRAP_FLAG; ++ rc3 = VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.Ctls.u32ProcCtls); ++ ++ //TODO: restoreOldFlags ++ PGMShwRestoreRights(pVCpu, GCPhys); ++ //Flush TLB ++ //VMXR0InvalidatePhysPage(pVM, pVCpu, GCPhys); ++ hmR0VmxFlushEpt(pVCpu, pVM->hm.s.vmx.enmTlbFlushEpt); ++ //RTSpinlockRelease(pVM->mystate.s.PageSpinlock); ++ ++ return VINF_SUCCESS; ++ } ++ int SoftBreakpointId = VMMGetBreakpointIdFromPage(pVM, GCPhys, FDP_SOFTHBP); ++ if(SoftBreakpointId > 0){ ++ //FDP_SOFTHBP ++ ASMAtomicUoOrU64(&pVCpu->hm.s.fCtxChanged, HM_CHANGED_GUEST_RIP ++ | HM_CHANGED_GUEST_RSP ++ | HM_CHANGED_GUEST_RFLAGS); ++ ++ //Avoid stack overflow when Fault inside fault ! ++ if(pVCpu->mystate.s.bPageFaultOverflowGuard){ ++ STAM_COUNTER_INC(&pVCpu->hm.s.StatExitGuestGP); ++ return VINF_SUCCESS; ++ } ++ ++ //RTSpinlockAcquire(pVM->mystate.s.PageSpinlock); ++ pVCpu->mystate.s.bPageFaultOverflowGuard = true; ++ bool bWriteAccess = ((pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_DATA_WRITE) != 0); ++ rc = VINF_SUCCESS; ++ ++ //Execute Access ++ if(pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_INSTR_FETCH) ++ { ++ //Execute only on ModPage ++ PGMShwSetHCPage(pVCpu, GCPhys, pVM->bp.l[SoftBreakpointId].breakpointHardwarePage->HCPhys); ++ PGMShwNoPresent(pVCpu, GCPhys); ++ PGMShwNoWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); //Execute ! ++ }else { ++ //Read or Write Access ++ //if((pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_DATA_READ) ++ // || (pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_DATA_WRITE)) ++ //{ ++ //Read, Write on OriginalPage ++ PGMShwSetHCPage(pVCpu, GCPhys, pVM->bp.l[SoftBreakpointId].breakpointOrigHCPhys); ++ PGMShwPresent(pVCpu, GCPhys); //Read ! ++ PGMShwWrite(pVCpu, GCPhys); //Write ! ++ PGMShwNoExecute(pVCpu, GCPhys); ++ //} ++ } ++ ++ //Trash case, TODO: What is this ? Why ? Maybe "mov [rax], rcx" and rax inside the page ++ if( ++ ((pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_DATA_READ) && (pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_DATA_WRITE) && (pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_INSTR_FETCH)) ++ ) ++ { //Special case ++ //Full rights on OriginalPage ++ PGMShwSetHCPage(pVCpu, GCPhys, pVM->bp.l[SoftBreakpointId].breakpointOrigHCPhys); ++ PGMShwPresent(pVCpu, GCPhys); ++ PGMShwWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); ++ //Invalidate the GPA ++ //VMXR0InvalidatePhysPage(pVM, pVCpu, GCPhys); ++ hmR0VmxFlushEpt(pVCpu, pVM->hm.s.vmx.enmTlbFlushEpt); ++ ++ //Active MTF ++ pVCpu->hm.s.vmx.Ctls.u32ProcCtls |= VMX_PROC_CTLS_MONITOR_TRAP_FLAG; ++ VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.Ctls.u32ProcCtls); ++ ++ //Single Step ++ //rc = VBOXSTRICTRC_VAL(hmR0VmxRunGuestCodeNormal(pVM, pVCpu, pMixedCtx)); ++ rc = VBOXSTRICTRC_VAL(hmR0VmxRunGuestCodeNormal(pVCpu)); ++ ++ //Disable MTF ++ pVCpu->hm.s.vmx.Ctls.u32ProcCtls &= ~VMX_PROC_CTLS_MONITOR_TRAP_FLAG; ++ VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.Ctls.u32ProcCtls); ++ ++ //Execute only on ModPage ++ PGMShwSetHCPage(pVCpu, GCPhys, pVM->bp.l[SoftBreakpointId].breakpointHardwarePage->HCPhys); ++ PGMShwNoPresent(pVCpu, GCPhys); ++ PGMShwNoWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); //Execute ! ++ }else{ ++ if(bWriteAccess == true){ ++ //TODO: OrignalPage, SingleStep, Copy OrignalPage to ModPage, Reinstall the HLT ++ } ++ } ++ ++ //Invalidate the page ++ //VMXR0InvalidatePhysPage(pVM, pVCpu, GCPhys); ++ hmR0VmxFlushEpt(pVCpu, pVM->hm.s.vmx.enmTlbFlushEpt); ++ ++ pVCpu->mystate.s.bPageFaultOverflowGuard = false; ++ //RTSpinlockRelease(pVM->mystate.s.PageSpinlock); ++ ++ return rc; ++ } ++ } ++ /*ENDMYCODE*/ ++ + /* Same case as PGMR0Trap0eHandlerNPMisconfig(). See comment above, @bugref{6043}. */ + if ( rcStrict2 == VINF_SUCCESS + || rcStrict2 == VERR_PAGE_TABLE_NOT_PRESENT +@@ -13110,6 +13494,59 @@ static int hmR0VmxExitXcptBP(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient) + int rc = HMVMX_CPUMCTX_IMPORT_STATE(pVCpu, HMVMX_CPUMCTX_EXTRN_ALL); + AssertRCReturn(rc, rc); + ++ ++ /*MYCODE*/ ++ { ++ ++ PCPUMCTX pMixedCtx = &pVCpu->cpum.GstCtx; ++ int rc2 = hmR0VmxReadExitIntInfoVmcs(pVmxTransient); ++ rc2 |= hmR0VmxReadExitIntErrorCodeVmcs(pVmxTransient); ++ rc2 |= hmR0VmxReadExitInstrLenVmcs(pVmxTransient); ++ PVM pVM = pVCpu->CTX_SUFF(pVM); ++ uint64_t GCPhys; ++ PGMPhysGCPtr2GCPhys(pVCpu, pMixedCtx->rip, &GCPhys); ++ int SoftBreakpointId = VMMGetBreakpointId(pVM, GCPhys, FDP_SOFTHBP, FDP_EXECUTE_BP); ++ if(SoftBreakpointId >= 0){ ++ if(pVM->bp.l[SoftBreakpointId].breakpointCr3 == 0 ++ || pVM->bp.l[SoftBreakpointId].breakpointCr3 == CPUMGetGuestCR3(pVCpu)){ ++ pVCpu->mystate.s.bSoftHyperBreakPointHitted = true; ++ return VINF_EM_HALT; ++ }else{ ++ //This breakpoint is filtered ++ //Full rights on OriginalPage ++ PGMShwSetHCPage(pVCpu, GCPhys, pVM->bp.l[SoftBreakpointId].breakpointOrigHCPhys); ++ PGMShwPresent(pVCpu, GCPhys); ++ PGMShwWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); ++ //Invalidate the GPA ++ //VMXR0InvalidatePhysPage(pVM, pVCpu, GCPhys); ++ hmR0VmxFlushEpt(pVCpu, pVM->hm.s.vmx.enmTlbFlushEpt); ++ ++ //Enable MTF ++ pVCpu->hm.s.vmx.Ctls.u32ProcCtls |= VMX_PROC_CTLS_MONITOR_TRAP_FLAG; ++ rc2 = VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVCpu->hm.s.vmx.Ctls.u32ProcCtls); ++ ++ //Single Step ++ hmR0VmxRunGuestCodeNormal(pVCpu); ++ rc2 = hmR0VmxReadExitQualVmcs(pVCpu, pVmxTransient); ++ //rc2 |= hmR0VmxSaveGuestSegmentRegs(pVCpu, pMixedCtx); ++ rc2 |= HMVMX_CPUMCTX_IMPORT_STATE(pVCpu, CPUMCTX_EXTRN_SREG_MASK | CPUMCTX_EXTRN_DR7); ++ //rc2 |= hmR0VmxSaveGuestDR7(pVCpu, pMixedCtx); ++ ++ //Disable MTF ++ pVCpu->hm.s.vmx.Ctls.u32ProcCtls &= ~VMX_PROC_CTLS_MONITOR_TRAP_FLAG; ++ ++ //Execute only on ModPage ++ PGMShwSetHCPage(pVCpu, GCPhys, pVM->bp.l[SoftBreakpointId].breakpointHardwarePage->HCPhys); ++ PGMShwNoPresent(pVCpu, GCPhys); ++ PGMShwNoWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); //Execute ! ++ return VINF_SUCCESS; ++ } ++ } ++ } ++ /*ENDMYCODE*/ ++ + PCPUMCTX pCtx = &pVCpu->cpum.GstCtx; + rc = DBGFRZTrap03Handler(pVCpu->CTX_SUFF(pVM), pVCpu, CPUMCTX2CORE(pCtx)); + if (rc == VINF_EM_RAW_GUEST_TRAP) +@@ -13167,6 +13604,29 @@ static int hmR0VmxExitXcptDB(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient) + uint64_t uDR6 = X86_DR6_INIT_VAL; + uDR6 |= (pVmxTransient->uExitQual & (X86_DR6_B0 | X86_DR6_B1 | X86_DR6_B2 | X86_DR6_B3 | X86_DR6_BD | X86_DR6_BS)); + ++ /*MYCODE*/ ++ { ++ PCPUMCTX pMixedCtx = &pVCpu->cpum.GstCtx; ++ //If it is a breakpoint we handle it ++ if((uDR6 & (X86_DR6_B0 | X86_DR6_B1 | X86_DR6_B2 | X86_DR6_B3))){ ++ //Update DR6 ! ++ VMMRZCallRing3Disable(pVCpu); ++ HM_DISABLE_PREEMPT(); ++ ++ pMixedCtx->dr[6] &= ~X86_DR6_B_MASK; ++ pMixedCtx->dr[6] |= uDR6; ++ if (CPUMIsGuestDebugStateActive(pVCpu)) ++ ASMSetDR6(pMixedCtx->dr[6]); ++ ++ HM_RESTORE_PREEMPT(); ++ VMMRZCallRing3Enable(pVCpu); ++ ++ pVCpu->mystate.s.bHardHyperBreakPointHitted = true; ++ return VINF_EM_HALT; ++ } ++ } ++ /*ENDMYCODE*/ ++ + PCPUMCTX pCtx = &pVCpu->cpum.GstCtx; + rc = DBGFRZTrap01Handler(pVCpu->CTX_SUFF(pVM), pVCpu, CPUMCTX2CORE(pCtx), uDR6, pVCpu->hm.s.fSingleInstruction); + Log6Func(("rc=%Rrc\n", rc)); +diff --git a/src/VBox/VMM/VMMR0/VMMR0.cpp b/src/VBox/VMM/VMMR0/VMMR0.cpp +index 4f5d1c2b..dd3e17bf 100644 +--- a/src/VBox/VMM/VMMR0/VMMR0.cpp ++++ b/src/VBox/VMM/VMMR0/VMMR0.cpp +@@ -61,6 +61,13 @@ + #include + #include + ++/*MYCODE*/ ++#include ++#include ++#include ++#include ++/*ENDMYCODE*/ ++ + #include "dtrace/VBoxVMM.h" + + +@@ -2427,6 +2434,47 @@ static int vmmR0EntryExWorker(PGVM pGVM, PVM pVM, VMCPUID idCpu, VMMR0OPERATION + VMM_CHECK_SMAP_CHECK2(pVM, RT_NOTHING); + break; + #endif ++ /*MYCODE*/ ++ case VMMR0_DO_ALLOC_HCPHYS: ++ { ++ ALLOCPAGEREQ* pReq = (ALLOCPAGEREQ*)pReqHdr; ++ if(pReq == NULL){ ++ LogRel(("[WDEBUG] pReqHdr is NULL\n")); ++ return -2; ++ } ++ int rc = 0; ++ RTR0MEMOBJ hMemObjMod; ++ //Allocates a new physical page ++ //rc = RTR0MemObjAllocPhysEx(&hMemObjMod, pReq->newPageSize, NIL_RTHCPHYS, pReq->newPageSize); ++ //rc = RTR0MemObjAllocPhysEx(&hMemObjMod, 4096, NIL_RTHCPHYS, 4096); ++ rc = RTR0MemObjAllocLow(&hMemObjMod, pReq->newPageSize, false); ++ if(RT_SUCCESS(rc)){ ++ //Maps the new page in ring-0 address space ++ RTR0MEMOBJ hMapObjMod; ++ rc = RTR0MemObjMapKernel(&hMapObjMod, hMemObjMod, (void *)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE); ++ //Maps the new page in ring-3 address space ++ int rc2 = RTR0MemObjMapUser(&hMapObjMod, hMemObjMod, (RTR3PTR)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE, NIL_RTR0PROCESS); ++ LogRel(("[WEBUG] Mappin %d %d\n", rc, rc2)); ++ if(RT_SUCCESS(rc) && RT_SUCCESS(rc2)){ ++ //Gets the ring-0 address of the new page ++ //pVM->args.allochcphysreq.R0Ptr = (uint8_t*)RTR0MemObjAddress(hMapObjMod); ++ //Gets the ring-3 address of the new page ++ pReq->newPageR3Ptr = (uint8_t*)RTR0MemObjAddressR3(hMapObjMod); ++ //Gets the physical address of the new page ++ pReq->newPageHCPHys = RTR0MemObjGetPagePhysAddr(hMapObjMod, 0); ++ return 0; ++ }else{ ++ LogRel(("[WDEBUG] Map failed ! \n")); ++ return -3; ++ } ++ }else{ ++ LogRel(("[WDEBUG] Alloc failed !\n")); ++ return rc; ++ } ++ return -4; ++ } ++ return VERR_NOT_SUPPORTED; ++ /*ENMYCODE*/ + default: + /* + * We're returning VERR_NOT_SUPPORT here so we've got something else +diff --git a/src/VBox/VMM/VMMR3/CPUM.cpp b/src/VBox/VMM/VMMR3/CPUM.cpp +index 9f301998..7b543839 100644 +--- a/src/VBox/VMM/VMMR3/CPUM.cpp ++++ b/src/VBox/VMM/VMMR3/CPUM.cpp +@@ -2077,6 +2077,10 @@ VMMR3DECL(int) CPUMR3Term(PVM pVM) + */ + VMMR3DECL(void) CPUMR3ResetCpu(PVM pVM, PVMCPU pVCpu) + { ++ /*MYCODE*/ ++ pVCpu->mystate.s.bRestoreRequired = false; ++ pVCpu->mystate.s.bPauseRequired = false; ++ /*ENDMYCODE*/ + /** @todo anything different for VCPU > 0? */ + PCPUMCTX pCtx = &pVCpu->cpum.s.Guest; + +diff --git a/src/VBox/VMM/VMMR3/EM.cpp b/src/VBox/VMM/VMMR3/EM.cpp +index a0660b0c..e1ebf699 100644 +--- a/src/VBox/VMM/VMMR3/EM.cpp ++++ b/src/VBox/VMM/VMMR3/EM.cpp +@@ -74,6 +74,9 @@ + #include + #include + ++/*MYCODE*/ ++#include ++/*ENDMYCODE*/ + + /********************************************************************************************************************************* + * Internal Functions * +@@ -141,6 +144,11 @@ VMMR3_INT_DECL(int) EMR3Init(PVM pVM) + pVM->em.s.fGuruOnTripleFault = true; + } + ++ /*MYCODE*/ ++ //Dont Guru on Triple Fault... This is annoying ! ++ pVM->em.s.fGuruOnTripleFault = false; ++ /*ENDMYCODE*/ ++ + LogRel(("EMR3Init: fRecompileUser=%RTbool fRecompileSupervisor=%RTbool fRawRing1Enabled=%RTbool fIemExecutesAll=%RTbool fGuruOnTripleFault=%RTbool\n", + pVM->fRecompileUser, pVM->fRecompileSupervisor, pVM->fRawRing1Enabled, pVM->em.s.fIemExecutesAll, pVM->em.s.fGuruOnTripleFault)); + +@@ -901,7 +909,9 @@ static VBOXSTRICTRC emR3Debug(PVM pVM, PVMCPU pVCpu, VBOXSTRICTRC rc) + * Simple events: stepped, breakpoint, stop/assertion. + */ + case VINF_EM_DBG_STEPPED: +- rc = DBGFR3Event(pVM, DBGFEVENT_STEPPED); ++ /*MYCODE*/ ++ //rc = DBGFR3Event(pVM, DBGFEVENT_STEPPED); ++ /*ENDMYCODE*/ + break; + + case VINF_EM_DBG_BREAKPOINT: +@@ -2514,6 +2524,28 @@ VMMR3_INT_DECL(int) EMR3ExecuteVM(PVM pVM, PVMCPU pVCpu) + else if (fFFDone) + fFFDone = false; + ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired){ ++ //Set the active CPU as STATE_PAUSED ++ pVCpu->mystate.s.u8StateBitmap |= FDP_STATE_PAUSED; ++ //LogRel(("[WDEBUG] CPU[%d] Entering PAUSE in EM!\n", pVCpu->idCpu)); ++ VMR3EnterPause(pVM, pVCpu); ++ //LogRel(("[WDEBUG] CPU[%d] Leaving PAUSE in EM!\n", pVCpu->idCpu)); ++ rc = VINF_SUCCESS; ++ } ++ pVCpu->mystate.s.u8StateBitmap &= ~FDP_STATE_PAUSED; ++ pVCpu->mystate.s.u64TickCount++; ++ if(pVCpu->mystate.s.bRebootRequired){ ++ rc = VINF_EM_TRIPLE_FAULT; ++ pVM->em.s.fGuruOnTripleFault = false; ++ pVCpu->mystate.s.bRebootRequired = false; ++ } ++ if(pVCpu->mystate.s.bSuspendRequired){ ++ rc = VINF_EM_SUSPEND; ++ pVCpu->mystate.s.bSuspendRequired = false; ++ } ++ /*MYCODE*/ ++ + /* + * Now what to do? + */ +@@ -3066,6 +3098,21 @@ VMMR3_INT_DECL(int) EMR3ExecuteVM(PVM pVM, PVMCPU pVCpu) + /* not reached */ + } + ++/*MYCODE*/ ++VMMR3_INT_DECL(int) EMR3ProcessForcedAction(PVM pVM, PVMCPU pVCpu, int rc) ++{ ++ rc = emR3ForcedActions(pVM, pVCpu, rc); ++ VBOXVMM_EM_FF_ALL_RET(pVCpu, rc); ++ ++ EMSTATE enmState = emR3Reschedule(pVM, pVCpu); ++ //LogRel(("EMR3ExecuteVM: VINF_EM_RESCHEDULE: %d -> %d (%s)\n", 0, enmState, "emR3GetStateName(enmState)")); ++ if (pVCpu->em.s.enmState != enmState && enmState == EMSTATE_IEM_THEN_REM) ++ pVCpu->em.s.cIemThenRemInstructions = 0; ++ pVCpu->em.s.enmState = enmState; ++ return rc; ++} ++/*ENDMYCODE*/ ++ + /** + * Notify EM of a state change (used by FTM) + * +diff --git a/src/VBox/VMM/VMMR3/EMHM.cpp b/src/VBox/VMM/VMMR3/EMHM.cpp +index 2b03d20b..ad6d7b6f 100644 +--- a/src/VBox/VMM/VMMR3/EMHM.cpp ++++ b/src/VBox/VMM/VMMR3/EMHM.cpp +@@ -497,6 +497,10 @@ int emR3HmExecute(PVM pVM, PVMCPU pVCpu, bool *pfFFDone) + break; + } + } ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired) ++ break; ++ /*ENDMYCODE*/ + } + + /* +diff --git a/src/VBox/VMM/VMMR3/MM.cpp b/src/VBox/VMM/VMMR3/MM.cpp +index 53dcbb94..ff91eb01 100644 +--- a/src/VBox/VMM/VMMR3/MM.cpp ++++ b/src/VBox/VMM/VMMR3/MM.cpp +@@ -854,3 +854,7 @@ VMMR3DECL(uint32_t) MMR3PhysGet4GBRamHoleSize(PVM pVM) + return pVM->mm.s.cbRamHole; + } + ++VMMR3DECL(uint64_t) MMR3PhysGetRamSizeU(PUVM pUVM) ++{ ++ return pUVM->pVM->mm.s.cbRamBase; ++} +\ No newline at end of file +diff --git a/src/VBox/VMM/VMMR3/PGMDbg.cpp b/src/VBox/VMM/VMMR3/PGMDbg.cpp +index 8cf09fe4..040d1a90 100644 +--- a/src/VBox/VMM/VMMR3/PGMDbg.cpp ++++ b/src/VBox/VMM/VMMR3/PGMDbg.cpp +@@ -779,6 +779,10 @@ VMMR3_INT_DECL(int) PGMR3DbgScanPhysical(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cbRa + return VERR_DBGF_MEM_NOT_FOUND; + } + ++VMMR3_INT_DECL(int) PGMR3DbgScanPhysicalU(PUVM pUVM, RTGCPHYS GCPhys, RTGCPHYS cbRange, RTGCPHYS GCPhysAlign, const uint8_t *pabNeedle, size_t cbNeedle, PRTGCPHYS pGCPhysHit) ++{ ++ return PGMR3DbgScanPhysical(pUVM->pVM, GCPhys, cbRange, GCPhysAlign, pabNeedle, cbNeedle, pGCPhysHit); ++} + + /** + * Scans (guest) virtual memory for a byte string. +diff --git a/src/VBox/VMM/VMMR3/TRPM.cpp b/src/VBox/VMM/VMMR3/TRPM.cpp +index 628541af..581c697e 100644 +--- a/src/VBox/VMM/VMMR3/TRPM.cpp ++++ b/src/VBox/VMM/VMMR3/TRPM.cpp +@@ -1498,6 +1498,14 @@ VMMR3DECL(bool) TRPMR3IsGateHandler(PVM pVM, RTRCPTR GCPtr) + */ + VMMR3DECL(int) TRPMR3InjectEvent(PVM pVM, PVMCPU pVCpu, TRPMEVENT enmEvent, bool *pfInjected) + { ++ /*MYCODE*/ ++ //Avoid interrupt during restore or pause ++ if(pVCpu->mystate.s.bRestoreRequired ++ || pVCpu->mystate.s.bPauseRequired ++ || pVCpu->mystate.s.bDisableInterrupt){ ++ return VINF_EM_RESCHEDULE_HM; ++ } ++ /*ENDMYCODE*/ + #ifdef VBOX_WITH_RAW_MODE + PCPUMCTX pCtx = CPUMQueryGuestCtxPtr(pVCpu); + Assert(!PATMIsPatchGCAddr(pVM, pCtx->eip)); +diff --git a/src/VBox/VMM/VMMR3/VM.cpp b/src/VBox/VMM/VMMR3/VM.cpp +index 59d6359a..3ffe1ff1 100644 +--- a/src/VBox/VMM/VMMR3/VM.cpp ++++ b/src/VBox/VMM/VMMR3/VM.cpp +@@ -93,6 +93,12 @@ + #include + #include + ++/*MYCODE*/ ++#include ++#include ++#include ++/*ENDMYCODE*/ ++ + + /********************************************************************************************************************************* + * Internal Functions * +@@ -626,6 +632,18 @@ static int vmR3CreateU(PUVM pUVM, uint32_t cCpus, PFNCFGMCONSTRUCTOR pfnCFGMCons + rc = vmR3ReadBaseConfig(pVM, pUVM, cCpus); + if (RT_SUCCESS(rc)) + { ++ /*MYCODE*/ ++ //This spinlock is created in Ring-0! ++ strcpy(pVM->mystate.s.PageSpinLockName, "PAGELOCK_"); ++ strcat(pVM->mystate.s.PageSpinLockName, VMR3GetName(pUVM)); ++ ++ //Create CpuSpinLock ++ char CpuSpinLockName[256]; ++ strcpy(CpuSpinLockName, "CPULOCK_"); ++ strcat(CpuSpinLockName, VMR3GetName(pUVM)); ++ pVM->mystate.s.CpuLock = NIL_RTSPINLOCK; ++ RTSpinlockCreate(&pVM->mystate.s.CpuLock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, CpuSpinLockName); ++ /*ENDMYCODE*/ + /* + * Init the ring-3 components and ring-3 per cpu data, finishing it off + * by a relocation round (intermediate context finalization will do this). +@@ -1429,6 +1447,12 @@ static DECLCALLBACK(VBOXSTRICTRC) vmR3Resume(PVM pVM, PVMCPU pVCpu, void *pvUser + VMRESUMEREASON enmReason = (VMRESUMEREASON)(uintptr_t)pvUser; + LogFlow(("vmR3Resume: pVM=%p pVCpu=%p/#%u enmReason=%d\n", pVM, pVCpu, pVCpu->idCpu, enmReason)); + ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bRestoreRequired == true){ ++ pVCpu->mystate.s.bPauseRequired = true; ++ } ++ /*ENDMYCODE*/ ++ + /* + * The first thread thru here tries to change the state. We shouldn't be + * called again if this fails. +@@ -2215,6 +2239,27 @@ VMMR3_INT_DECL(int) VMR3LoadFromStreamFT(PUVM pUVM, PCSSMSTRMOPS pStreamOps, voi + */ + static DECLCALLBACK(VBOXSTRICTRC) vmR3PowerOff(PVM pVM, PVMCPU pVCpu, void *pvUser) + { ++ /*MYCODE*/ ++ //PVMCPU pVCpu = &pUVM->pVM->aCpus[0]; ++ PUVM pUVM = pVM->pUVM; ++ //Do this only if the vCpu is Paused ++ if(pVCpu->mystate.s.bPauseRequired == true){ ++ //Avoid freeze ++ //VMR3Break(pUVM); ++ //Clear All Breakpoint ++ for(int BreakpointId=0; BreakpointIdmystate.s.bPauseRequired = false; ++ ++ //Stop FDP Debugger ++ FDP_SHM *pFdpShm = (FDP_SHM *)pUVM->pVM->mystate.s.pFdpShm; ++ pFdpShm->pFdpServer->bIsRunning = false; ++ } ++ /*ENDMYCODE*/ + LogFlow(("vmR3PowerOff: pVM=%p pVCpu=%p/#%u\n", pVM, pVCpu, pVCpu->idCpu)); + Assert(!pvUser); NOREF(pvUser); + +diff --git a/src/VBox/VMM/VMMR3/VMEmt.cpp b/src/VBox/VMM/VMMR3/VMEmt.cpp +index 7c56c5b3..d79c0fe4 100644 +--- a/src/VBox/VMM/VMMR3/VMEmt.cpp ++++ b/src/VBox/VMM/VMMR3/VMEmt.cpp +@@ -43,6 +43,18 @@ + #include + #include + ++/*MYCODE*/ ++#include ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++/*ENDMYCODE*/ ++ + + /********************************************************************************************************************************* + * Internal Functions * +@@ -379,12 +391,23 @@ static DECLCALLBACK(int) vmR3HaltOldDoHalt(PUVMCPU pUVCpu, const uint32_t fMask, + if ( VM_FF_IS_ANY_SET(pVM, VM_FF_EXTERNAL_HALTED_MASK) + || VMCPU_FF_IS_ANY_SET(pVCpu, fMask)) + break; ++ ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired == true) ++ break; ++ /*ENDMYCODE*/ ++ + uint64_t u64NanoTS; + TMTimerPollGIP(pVM, pVCpu, &u64NanoTS); + if ( VM_FF_IS_ANY_SET(pVM, VM_FF_EXTERNAL_HALTED_MASK) + || VMCPU_FF_IS_ANY_SET(pVCpu, fMask)) + break; + ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired == true) ++ break; ++ /*ENDMYCODE*/ ++ + /* + * Wait for a while. Someone will wake us up or interrupt the call if + * anything needs our attention. +@@ -728,6 +751,11 @@ static DECLCALLBACK(int) vmR3HaltGlobal1Halt(PUVMCPU pUVCpu, const uint32_t fMas + || VMCPU_FF_IS_ANY_SET(pVCpu, fMask)) + break; + ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired == true) ++ break; ++ /*ENDMYCODE*/ ++ + /* + * Estimate time left to the next event. + */ +@@ -738,6 +766,11 @@ static DECLCALLBACK(int) vmR3HaltGlobal1Halt(PUVMCPU pUVCpu, const uint32_t fMas + || VMCPU_FF_IS_ANY_SET(pVCpu, fMask)) + break; + ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired == true) ++ break; ++ /*ENDMYCODE*/ ++ + /* + * Block if we're not spinning and the interval isn't all that small. + */ +@@ -1097,11 +1130,928 @@ VMMR3_INT_DECL(void) VMR3NotifyGlobalFFU(PUVM pUVM, uint32_t fFlags) + VMMR3_INT_DECL(void) VMR3NotifyCpuFFU(PUVMCPU pUVCpu, uint32_t fFlags) + { + PUVM pUVM = pUVCpu->pUVM; +- + LogFlow(("VMR3NotifyCpuFFU:\n")); + g_aHaltMethods[pUVM->vm.s.iHaltMethod].pfnNotifyCpuFF(pUVCpu, fFlags); + } + ++/*MYCODE*/ ++//TODO: include with DBGTcp.cpp ++#define DEBUG_LEVEL 1 ++ ++#if DEBUG_LEVEL > 0 ++#define LogRelDebug(x) LogRel(x) ++#else ++#define LogRelDebug(x) ++#endif ++ ++//TODO: Move this in VMMALL as there is a copy in VMMR0 ++//TODO: Add Cs, Ds, Es, Fs, Gs, SS, ... ++void VMR3UpdateFdpCpuCtx(PVMCPU pVCpu) ++{ ++ PCCPUMCTXCORE pCtxCore = CPUMGetGuestCtxCore(pVCpu); ++ FDP_CPU_CTX* pFdpCpuCtx = (FDP_CPU_CTX *)pVCpu->mystate.s.pCpuShm; ++ ++ pFdpCpuCtx->rip = pCtxCore->rip; ++ pFdpCpuCtx->rax = pCtxCore->rax; ++ pFdpCpuCtx->rcx = pCtxCore->rcx; ++ pFdpCpuCtx->rdx = pCtxCore->rdx; ++ pFdpCpuCtx->rbx = pCtxCore->rbx; ++ pFdpCpuCtx->rsp = pCtxCore->rsp; ++ pFdpCpuCtx->rbp = pCtxCore->rbp; ++ pFdpCpuCtx->rsi = pCtxCore->rsi; ++ pFdpCpuCtx->rdi = pCtxCore->rdi; ++ pFdpCpuCtx->r8 = pCtxCore->r8; ++ pFdpCpuCtx->r9 = pCtxCore->r9; ++ pFdpCpuCtx->r10 = pCtxCore->r10; ++ pFdpCpuCtx->r11 = pCtxCore->r11; ++ pFdpCpuCtx->r12 = pCtxCore->r12; ++ pFdpCpuCtx->r13 = pCtxCore->r13; ++ pFdpCpuCtx->r14 = pCtxCore->r14; ++ pFdpCpuCtx->r15 = pCtxCore->r15; ++ pFdpCpuCtx->cr0 = CPUMGetGuestCR0(pVCpu); ++ pFdpCpuCtx->cr2 = CPUMGetGuestCR2(pVCpu); ++ pFdpCpuCtx->cr3 = CPUMGetGuestCR3(pVCpu); ++ pFdpCpuCtx->cr4 = CPUMGetGuestCR4(pVCpu); ++} ++ ++HardwarePage_t* VMR3GetAllocatedHardwarePage(PUVM pUVM, uint64_t GCPhys) ++{ ++ //Look for a breakpoint already using a convient page ++ int BreakpointId = VMMGetBreakpointIdFromPage(pUVM->pVM, GCPhys, FDP_SOFTHBP); ++ if(BreakpointId >= 0 ++ && BreakpointId < MAX_BREAKPOINT_ID){ ++ //A breakpoint using a convient page exists ++ pUVM->pVM->bp.l[BreakpointId].breakpointHardwarePage->ReferenceCount++; ++ return pUVM->pVM->bp.l[BreakpointId].breakpointHardwarePage; ++ } ++ ++ //Look in the Free HardwarePage table ++ for(uint32_t i=0; ipVM->mystate.s.u32HardwarePageTableCount; i++){ ++ if(pUVM->pVM->mystate.s.aHardwarePageTable[i].ReferenceCount == 0 ++ && pUVM->pVM->mystate.s.aHardwarePageTable[i].HCPhys != 0 ++ && pUVM->pVM->mystate.s.aHardwarePageTable[i].R3Ptr != NULL){ ++ pUVM->pVM->mystate.s.aHardwarePageTable[i].ReferenceCount = 1; ++ return &pUVM->pVM->mystate.s.aHardwarePageTable[i]; ++ } ++ } ++ ++ LogRel(("[WDEBUG] Allocate a new HardwarePage for %p !\n", GCPhys)); ++ //None are free, allocate a new one ! ++ ALLOCPAGEREQ Req; ++ Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; ++ Req.Hdr.cbReq = sizeof(Req); ++ Req.newPageSize = _4K; ++ int rc = SUPR3CallVMMR0Ex(pUVM->pVM->pVMR0, NIL_VMCPUID, VMMR0_DO_ALLOC_HCPHYS, 0, &Req.Hdr); ++ if(rc != 0){ ++ LogRel(("[WDEBUG] Failed to allocate a new HardwarePage %d\n", rc)); ++ return NULL; ++ } ++ ++ HardwarePage_t* TmpHardwarePage = &pUVM->pVM->mystate.s.aHardwarePageTable[pUVM->pVM->mystate.s.u32HardwarePageTableCount]; ++ ++ TmpHardwarePage->PageSize = _4K; ++ TmpHardwarePage->HCPhys = Req.newPageHCPHys; ++ TmpHardwarePage->R3Ptr = Req.newPageR3Ptr; ++ TmpHardwarePage->ReferenceCount = 1; ++ ++ pUVM->pVM->mystate.s.u32HardwarePageTableCount++; ++ ++ return TmpHardwarePage; ++} ++ ++ ++/* ++ * @brief: Restore all Original HCPhys for SoftHyperBreakpointed GCPhys ++ */ ++VMMR3_INT_DECL(int) VMR3RestoreAllOriginalPage(PUVM pUVM, bool bIsRead, bool bIsWrite, bool bIsExecute) ++{ ++ PVM pVM = pUVM->pVM; ++ PVMCPU pVCpu = &pVM->aCpus[0]; ++ BreakpointEntrie_t *pTempBreakpointEntrie = NULL; ++ for(uint8_t BreakpointId=4*pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ //Check if Breakpoint is Activated and if it is a SoftWareBreakpoint ++ if(pTempBreakpointEntrie->breakpointActivated == true && ++ pTempBreakpointEntrie->breakpointType == FDP_SOFTHBP && ++ pTempBreakpointEntrie->breakpointOrigHCPhys != 0x0 && ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable[0].Start != 0x0){ ++ //Set Original Page as Read and Write ++ uint64_t GCPhys = pTempBreakpointEntrie->breakpointGCPhysAreaTable[0].Start; ++ PGMShwSetHCPage(pVCpu, GCPhys, pTempBreakpointEntrie->breakpointOrigHCPhys); ++ if(bIsRead == true){ ++ PGMShwPresent(pVCpu, GCPhys); ++ }else{ ++ PGMShwNoPresent(pVCpu, GCPhys); ++ } ++ if(bIsWrite == true){ ++ PGMShwWrite(pVCpu, GCPhys); ++ }else{ ++ PGMShwNoWrite(pVCpu, GCPhys); ++ } ++ if(bIsExecute == true){ ++ PGMShwExecute(pVCpu, GCPhys); ++ }else{ ++ PGMShwNoExecute(pVCpu, GCPhys); ++ } ++ //Set page as breakable ! ++ PGMShwSetBreakable(pVCpu, GCPhys, true); ++ //Invalidate the page ! ++ PGMShwInvalidate(pVCpu, GCPhys); ++ } ++ } ++ return 0; ++} ++ ++VMMR3_INT_DECL(int) VMR3AddMsrBreakpoint(PUVM pUVM, uint8_t BreakpointAccessType, uint64_t BreakpointAddress) ++{ ++ PVM pVM = pUVM->pVM; ++ //Look for a free breakpoint ++ for(int BreakpointId=4*pUVM->pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == false){ ++ pTempBreakpointEntrie->breakpointActivated = true; ++ pTempBreakpointEntrie->breakpointGCPtr = BreakpointAddress; ++ pTempBreakpointEntrie->breakpointOrigHCPhys = 0x0; ++ pTempBreakpointEntrie->breakpointType = FDP_MSRHBP; ++ pTempBreakpointEntrie->breakpointLength = 1; ++ pTempBreakpointEntrie->breakpointAccessType = BreakpointAccessType; ++ pTempBreakpointEntrie->breakpointPageSize = 0x0; ++ pTempBreakpointEntrie->breakpointHardwarePage = NULL; ++ pTempBreakpointEntrie->breakpointGCPhysAreaCount = 0; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = NULL; ++ ++ return BreakpointId; ++ } ++ } ++ return -1; ++} ++ ++VMMR3_INT_DECL(int) VMR3AddCrBreakpoint(PUVM pUVM, uint8_t BreakpointAccessType, uint64_t BreakpointAddress) ++{ ++ PVM pVM = pUVM->pVM; ++ //Look for a free breakpoint ++ for(int BreakpointId=4*pUVM->pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == false){ ++ pTempBreakpointEntrie->breakpointActivated = true; ++ pTempBreakpointEntrie->breakpointGCPtr = BreakpointAddress; ++ pTempBreakpointEntrie->breakpointOrigHCPhys = 0x0; ++ pTempBreakpointEntrie->breakpointType = FDP_CRHBP; ++ pTempBreakpointEntrie->breakpointLength = 1; ++ pTempBreakpointEntrie->breakpointAccessType = BreakpointAccessType; ++ pTempBreakpointEntrie->breakpointPageSize = 0x0; ++ pTempBreakpointEntrie->breakpointHardwarePage = NULL; ++ pTempBreakpointEntrie->breakpointGCPhysAreaCount = 0; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = NULL; ++ ++ return BreakpointId; ++ } ++ } ++ return -1; ++} ++ ++VMMR3_INT_DECL(int) VMR3AddSoftBreakpoint(PUVM pUVM, PVMCPU pVCpu, uint8_t BreakpointAddressType, uint64_t BreakpointAddress, uint64_t BreakpointCr3) ++{ //TODO: Move it to VMMAll ! ++ ++ LogRel(("[WDEBUG] VMR3AddSoftBreakpoint in\n")); ++ VMR3RestoreAllOriginalPage(pUVM, true, true, false); ++ ++ PVM pVM = pUVM->pVM; ++ ++ //Convert GCPtr to GCPhys if needed ++ uint64_t GCPhys; ++ uint64_t GCPtr = 0; ++ if(BreakpointAddressType == 0x1){//Virtual ++ GCPtr = BreakpointAddress; ++ PGMPhysGCPtr2GCPhys(pVCpu, BreakpointAddress, &GCPhys); ++ //PGMGstGetPage(pVCpu, BreakpointAddress, NULL, &GCPhys); ++ }else{ //Physical ++ GCPhys = BreakpointAddress; ++ } ++ ++ //Look for an already existing SoftHyperBreakpoint with same GCPhys ++ for(int BreakpointId=4*pUVM->pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true && ++ pTempBreakpointEntrie->breakpointType == FDP_SOFTHBP && ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable[0].Start == GCPhys){ ++ //We found one ! ++ return BreakpointId; ++ } ++ } ++ ++ for(int BreakpointId=4*pUVM->pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ ++ //Find a free breakpoint ++ if(pTempBreakpointEntrie->breakpointActivated == false){ ++ //Get Original HCPhys ++ uint64_t origHCPhys; ++ //It is the OriginalPage because we called VMR3RestoreAllOriginalPage(pUVM); ++ PGMShwGetHCPage(pVCpu, GCPhys, &origHCPhys); ++ //After restore EPTPTE are not initilized ++ if(origHCPhys < 0x100){ ++ LogRel(("[WDEBUG] AddSoft %p %p\n", GCPhys, origHCPhys)); ++ return -1; ++ } ++ ++ //Get a HardwarePage ++ HardwarePage_t* pTempHardwarePage = VMR3GetAllocatedHardwarePage(pUVM, GCPhys); ++ if(pTempHardwarePage != NULL){ ++ if(pTempHardwarePage->ReferenceCount == 1){ ++ //This is the first breakpoint using this hardware page ++ //Copy original page content to new page only if the page is new ++ LogRel(("[WDEBUG] Copy OrignalPage to ModificatedPage\n")); ++ PGMPhysSimpleReadGCPhys(pUVM->pVM, (void*)pTempHardwarePage->R3Ptr, (RTGCPHYS)(GCPhys & ~(pTempHardwarePage->PageSize-1)), pTempHardwarePage->PageSize); ++ } ++ ++ LogRel(("[WDEBUG] SoftHyperBreakpoint installation : \n")); ++ LogRel(("[WDEBUG] Original page HCPhys: 0x%p\n", origHCPhys)); ++ LogRel(("[WDEBUG] GCPhys: 0x%p\n", GCPhys)); ++ LogRel(("[WDEBUG] pTempHardwarePage: %p\n", pTempHardwarePage)); ++ LogRel(("[WDEBUG] Modificated page HCPhys: 0x%p\n", pTempHardwarePage->HCPhys)); ++ LogRel(("[WDEBUG] Modificated page R3Ptr: 0x%p\n", pTempHardwarePage->R3Ptr)); ++ LogRel(("[WDEBUG] HardwarePage Reference Count: %d\n", pTempHardwarePage->ReferenceCount)); ++ LogRel(("[WDEBUG] \n")); ++ ++ pTempBreakpointEntrie->breakpointActivated = true; ++ pTempBreakpointEntrie->breakpointGCPtr = GCPtr; ++ pTempBreakpointEntrie->breakpointOrigHCPhys = origHCPhys; ++ pTempBreakpointEntrie->breakpointType = FDP_SOFTHBP; ++ pTempBreakpointEntrie->breakpointLength = 1; ++ pTempBreakpointEntrie->breakpointCr3 = BreakpointCr3; ++ pTempBreakpointEntrie->breakpointAccessType = FDP_EXECUTE_BP; ++ pTempBreakpointEntrie->breakpointPageSize = pTempHardwarePage->PageSize; ++ pTempBreakpointEntrie->breakpointHardwarePage = pTempHardwarePage; ++ //Save the original byte ++ pTempBreakpointEntrie->breakpointOriginalByte = pTempHardwarePage->R3Ptr[(GCPhys & (pTempHardwarePage->PageSize-1))]; //TODO change % to & ++ //Install a HLT in the new page ++ pTempHardwarePage->R3Ptr[(GCPhys & (pTempHardwarePage->PageSize-1))] = 0xCC; ++ pTempBreakpointEntrie->breakpointGCPhysAreaCount = 1; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = (GCPhysArea_t*)malloc(1 * sizeof(GCPhysArea_t)); ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable[0].Start = GCPhys; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable[0].End = GCPhys+1; ++ ++ //Set page as original and read/write ++ PGMShwSetHCPage(pVCpu, GCPhys, origHCPhys); ++ PGMShwPresent(pVCpu, GCPhys); ++ PGMShwWrite(pVCpu, GCPhys); ++ PGMShwNoExecute(pVCpu, GCPhys); ++ //Set page as breakable ! ++ PGMShwSetBreakable(pVCpu, GCPhys, true); ++ //Invalidate the page ! ++ PGMShwInvalidate(pVCpu, GCPhys); ++ ++ return BreakpointId; ++ }else{ ++ LogRel(("[WDEBUG] Failed to Allocate HardwarePage\n")); ++ return -1; ++ } ++ } ++ } ++ return -1; ++} ++ ++void ApplyBreakpointOnPage(PVM pVM, PVMCPU pVCpu, uint64_t GCPhys, uint8_t BreakpointAccessType) ++{ ++ //No access at all for FDP_READ_BP ++ if(BreakpointAccessType & FDP_READ_BP){ ++ PGMShwNoPresent(pVCpu, GCPhys); ++ PGMShwNoWrite(pVCpu, GCPhys); ++ PGMShwNoExecute(pVCpu, GCPhys); ++ } ++ if(BreakpointAccessType & FDP_WRITE_BP) ++ PGMShwNoWrite(pVCpu, GCPhys); ++ if(BreakpointAccessType & FDP_EXECUTE_BP) ++ PGMShwNoExecute(pVCpu, GCPhys); ++ ++ //Set the page as Breakable page ++ PGMShwSetBreakable(pVCpu, GCPhys, true); ++ //Save the final page rights ++ PGMShwSaveRights(pVCpu, GCPhys); ++ //Invalidate the page ! ++ PGMShwInvalidate(pVCpu, GCPhys); ++} ++ ++void DisableBreakpointOnPage(PVM pVM, PVMCPU pVCpu, uint64_t GCPhys) ++{ ++ PGMShwPresent(pVCpu, GCPhys); ++ PGMShwWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); ++ ++ //Set the page as Standard page ++ PGMShwSetBreakable(pVCpu, GCPhys, false); ++ //Save the final page rights ++ PGMShwSaveRights(pVCpu, GCPhys); ++ //Invalidate the page ! ++ PGMShwInvalidate(pVCpu, GCPhys); ++} ++ ++#define MIN(a,b) (((a)<(b))?(a):(b)) ++ ++void AddGCPhysAreaInBreakpoint(BreakpointEntrie_t *pTempBreakpointEntrie, uint64_t Start, uint64_t End) ++{ ++ if(pTempBreakpointEntrie){ ++ int CurrentGCPhysAreaIndex = pTempBreakpointEntrie->breakpointGCPhysAreaCount; ++ //LogRel(("[WDEBUG] %d. %p->%p\n", CurrentGCPhysAreaIndex, Start, End)); ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable[CurrentGCPhysAreaIndex].Start = Start; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable[CurrentGCPhysAreaIndex].End = End; ++ pTempBreakpointEntrie->breakpointGCPhysAreaCount++; ++ } ++} ++ ++void DisableAllPageBreakpoint(PVM pVM, PVMCPU pVCpu) ++{ ++ //Restore all rights for pages in breakpoint ++ for(int BreakpointId=0; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_PAGEHBP ++ && pTempBreakpointEntrie->breakpointGCPhysAreaTable){ ++ for(int j=0; jbreakpointGCPhysAreaCount; j++){ ++ DisableBreakpointOnPage(pVM, pVCpu, pTempBreakpointEntrie->breakpointGCPhysAreaTable[j].Start); ++ } ++ } ++ } ++} ++ ++void EnableAllPageBreakpoint(PVM pVM, PVMCPU pVCpu) ++{ ++ //Restore all rights for pages in breakpoint ++ for(int BreakpointId=0; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_PAGEHBP ++ && pTempBreakpointEntrie->breakpointGCPhysAreaTable){ ++ //Apply on pages ++ for(int j=0; jbreakpointGCPhysAreaCount; j++){ ++ ApplyBreakpointOnPage(pVM, pVCpu, pTempBreakpointEntrie->breakpointGCPhysAreaTable[j].Start, pTempBreakpointEntrie->breakpointAccessType); ++ } ++ } ++ } ++} ++ ++void InstallAllPageBreakpoint(PVM pVM, PVMCPU pVCpu) ++{ ++ //Restore all rights for pages in breakpoint ++ for(int BreakpointId=0; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_PAGEHBP ++ && pTempBreakpointEntrie->breakpointGCPhysAreaTable){ ++ for(int j=0; jbreakpointGCPhysAreaCount; j++){ ++ DisableBreakpointOnPage(pVM, pVCpu, pTempBreakpointEntrie->breakpointGCPhysAreaTable[j].Start); ++ } ++ if(pTempBreakpointEntrie->breakpointGCPhysAreaTable) ++ free(pTempBreakpointEntrie->breakpointGCPhysAreaTable); ++ pTempBreakpointEntrie->breakpointGCPhysAreaCount = 0; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = NULL; ++ } ++ } ++ ++ //Remove rights for pages in breakpoint ++ for(int BreakpointId=0; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_PAGEHBP){ ++ uint64_t BreakpointLength = pTempBreakpointEntrie->breakpointLength; ++ if(pTempBreakpointEntrie->breakpointGCPtr > 0){ //VirtualAddress Breakpoint ++ uint64_t GCPhys; ++ uint64_t GCPtr = pTempBreakpointEntrie->breakpointGCPtr; ++ int MaxGCPhysAreaCount = (BreakpointLength/_4K) + 1; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = (GCPhysArea_t*)malloc(MaxGCPhysAreaCount * sizeof(GCPhysArea_t)); ++ ++ //First chunk Page ++ int rc = PGMPhysGCPtr2GCPhys(pVCpu, GCPtr, &GCPhys); ++ uint64_t GCPhysPageEnd = (GCPhys & 0xFFFFFFFFFFFFF000) + _4K; ++ uint64_t AlreadyBreakpointSize = MIN(GCPhysPageEnd - GCPhys, BreakpointLength); ++ if(RT_SUCCESS(rc)){ ++ AddGCPhysAreaInBreakpoint(pTempBreakpointEntrie, GCPhys, GCPhys+AlreadyBreakpointSize); ++ } ++ //Intermediate complete page ++ int64_t LeftToBreakpoint = BreakpointLength - AlreadyBreakpointSize; ++ while (LeftToBreakpoint >= _4K){ //More than 1 page to breakpoint ! ++ rc = PGMPhysGCPtr2GCPhys(pVCpu, GCPtr + AlreadyBreakpointSize, &GCPhys); ++ if(RT_SUCCESS(rc)){ ++ AddGCPhysAreaInBreakpoint(pTempBreakpointEntrie, GCPhys, GCPhys+_4K); ++ } ++ LeftToBreakpoint = LeftToBreakpoint - _4K; ++ AlreadyBreakpointSize = AlreadyBreakpointSize + _4K; ++ } ++ ++ //Last chunk page ++ if (LeftToBreakpoint > 0){ //Left breakpoint bytes ++ rc = PGMPhysGCPtr2GCPhys(pVCpu, GCPtr + AlreadyBreakpointSize, &GCPhys); ++ if(RT_SUCCESS(rc)){ ++ AddGCPhysAreaInBreakpoint(pTempBreakpointEntrie, GCPhys, GCPhys+LeftToBreakpoint); ++ } ++ } ++ }else{ //PhysicalAddress Breakpoint ++ uint64_t LeftToBreakpoint = 0; ++ int MaxGCPhysAreaCount = (BreakpointLength/_4K) + 1; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = (GCPhysArea_t*)malloc(MaxGCPhysAreaCount * sizeof(GCPhysArea_t)); ++ uint64_t GCPhysPageEnd = (pTempBreakpointEntrie->breakpointGCPhys & ~(_4K-1)) + _4K; ++ uint64_t LastPageEnd = MIN(GCPhysPageEnd, pTempBreakpointEntrie->breakpointGCPhys+BreakpointLength); ++ LeftToBreakpoint = BreakpointLength - (LastPageEnd - pTempBreakpointEntrie->breakpointGCPhys); ++ AddGCPhysAreaInBreakpoint(pTempBreakpointEntrie, pTempBreakpointEntrie->breakpointGCPhys, LastPageEnd); ++ while(LeftToBreakpoint >= _4K){ ++ AddGCPhysAreaInBreakpoint(pTempBreakpointEntrie, LastPageEnd, LastPageEnd+_4K); ++ LeftToBreakpoint -= _4K; ++ LastPageEnd += _4K; ++ } ++ if(LeftToBreakpoint > 0){ ++ AddGCPhysAreaInBreakpoint(pTempBreakpointEntrie, LastPageEnd, LastPageEnd+LeftToBreakpoint); ++ } ++ } ++ ++ ++ //Apply on pages ++ for(int j=0; jbreakpointGCPhysAreaCount; j++){ ++ ApplyBreakpointOnPage(pVM, pVCpu, pTempBreakpointEntrie->breakpointGCPhysAreaTable[j].Start, pTempBreakpointEntrie->breakpointAccessType); ++ } ++ } ++ } ++ ++ return; ++} ++ ++ ++bool IsOneCPURunning(PUVM pUVM) ++{ ++ for(uint32_t i=0; ipVM->aCpus[i].mystate.s.u8StateBitmap & FDP_STATE_PAUSED)){ ++ return true; ++ } ++ } ++ return false; ++} ++ ++ ++ ++ ++VMMR3_INT_DECL(int) VMR3AddPageBreakpoint(PUVM pUVM, PVMCPU pVCpu, uint8_t BreakpointId, uint8_t BreakpointAccessType, uint8_t BreakpointAddressType, uint64_t BreakpointAddress, uint64_t BreakpointLength) ++{ //TODO: Move it to VMMAll ! ++ if(IsOneCPURunning(pUVM) == true){ ++ //NO WAY !!!!!! ++ return -1; ++ } ++ ++ PVM pVM = pUVM->pVM; ++ ++ BreakpointEntrie_t *pTempBreakpointEntrie = NULL; ++ //If not a reserved to the guest breakpoint ++ if(BreakpointId < 0 || BreakpointId > 3){ ++ bool BreakpointIdFound = false; ++ for(BreakpointId=4*pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ //Find a free breakpoint ++ if(pTempBreakpointEntrie->breakpointActivated == false){ ++ break; ++ } ++ } ++ }else{ ++ pTempBreakpointEntrie = &pVM->bp.l[BreakpointId]; ++ } ++ ++ if(pTempBreakpointEntrie != NULL ++ && pTempBreakpointEntrie->breakpointActivated == false){ ++ uint64_t GCPhys; ++ uint64_t GCPtr = 0; ++ if(BreakpointAddressType == FDP_VIRTUAL_ADDRESS){//Virtual ++ GCPtr = BreakpointAddress; ++ int rc = PGMPhysGCPtr2GCPhys(pVCpu, BreakpointAddress, &GCPhys); ++ if(RT_FAILURE(rc)){ ++ //LogRel(("Fail to convert GCPtr(%p) -> GCphys\n", BreakpointAddress)); ++ return -1; ++ } ++ }else{ //Physical ++ GCPhys = BreakpointAddress; ++ } ++ ++ pVM->bp.l[BreakpointId].breakpointActivated = true; ++ pVM->bp.l[BreakpointId].breakpointGCPtr = GCPtr; ++ pVM->bp.l[BreakpointId].breakpointGCPhys = GCPhys; ++ pVM->bp.l[BreakpointId].breakpointType = FDP_PAGEHBP; ++ pVM->bp.l[BreakpointId].breakpointLength = BreakpointLength; ++ pVM->bp.l[BreakpointId].breakpointAccessType = BreakpointAccessType; ++ pVM->bp.l[BreakpointId].breakpointPageSize = _4K; ++ ++ InstallAllPageBreakpoint(pVM, pVCpu); ++ return BreakpointId; ++ } ++ return -1; ++} ++ ++//TODO: Move it to VMMAll ! ++VMMR3_INT_DECL(bool) VMR3RemoveBreakpoint(PUVM pUVM, int BreakpointId) ++{ ++ ++ if(BreakpointId < 0 || BreakpointId > MAX_BREAKPOINT_ID){ ++ return false; ++ } ++ ++ //If one Cpu is running, we can't remove a breakpoint ! ++ if(IsOneCPURunning(pUVM) == true){ ++ return false; ++ } ++ ++ PVM pVM = pUVM->pVM; ++ PVMCPU pVCpu = &pVM->aCpus[0]; ++ ++ //Restore OriginalPage for all SoftHyperBreakpoint ++ VMR3RestoreAllOriginalPage(pVM->pUVM, true, true, false); ++ ++ BreakpointEntrie_t *pTempBreakpointEntrie = &pVM->bp.l[BreakpointId]; ++ ++ if(pTempBreakpointEntrie->breakpointActivated == true){ ++ //Set the breakpoint as disabled ++ pTempBreakpointEntrie->breakpointActivated = false; ++ ++ switch(pTempBreakpointEntrie->breakpointType) ++ { ++ case FDP_PAGEHBP: ++ { ++ //Enable all rights for page in this breakpoint ++ for(int j=0; jbreakpointGCPhysAreaCount; j++){ ++ DisableBreakpointOnPage(pVM, pVCpu, pTempBreakpointEntrie->breakpointGCPhysAreaTable[j].Start); ++ } ++ //Enable all other page Breakpoint ++ InstallAllPageBreakpoint(pVM, pVCpu); ++ break; ++ } ++ case FDP_SOFTHBP: ++ { ++ pTempBreakpointEntrie->breakpointHardwarePage->ReferenceCount--; ++ if(pTempBreakpointEntrie->breakpointHardwarePage->ReferenceCount == 0){ ++ uint64_t GCPhys = pTempBreakpointEntrie->breakpointGCPhysAreaTable[0].Start; ++ LogRelDebug(("[WDEBUG] HardwarePage->ReferenceCount == 0\n")); ++ //No more breakpoint use this HardwarePage ! ++ //Restore Original page ++ PGMShwSetHCPage(pVCpu, GCPhys, pTempBreakpointEntrie->breakpointOrigHCPhys); ++ PGMShwPresent(pVCpu, GCPhys); ++ PGMShwWrite(pVCpu, GCPhys); ++ PGMShwExecute(pVCpu, GCPhys); ++ ++ PGMShwSetBreakable(pVCpu, GCPhys, false); ++ ++ PGMShwInvalidate(pVCpu, GCPhys); ++ } ++ break; ++ } ++ default: ++ break; ++ } ++ ++ pTempBreakpointEntrie->breakpointTag = 0; ++ pTempBreakpointEntrie->breakpointGCPtr = 0; ++ pTempBreakpointEntrie->breakpointType = 0; ++ pTempBreakpointEntrie->breakpointLength = 0; ++ pTempBreakpointEntrie->breakpointAccessType = 0x0; ++ pTempBreakpointEntrie->breakpointOrigHCPhys = 0x0; ++ pTempBreakpointEntrie->breakpointOriginalByte = 0x0; ++ pTempBreakpointEntrie->breakpointHardwarePage = NULL; ++ pTempBreakpointEntrie->breakpointPageSize = 0x0; ++ ++ if(pTempBreakpointEntrie->breakpointGCPhysAreaTable){ ++ free(pTempBreakpointEntrie->breakpointGCPhysAreaTable); ++ } ++ ++ pTempBreakpointEntrie->breakpointGCPhysAreaCount = 0; ++ pTempBreakpointEntrie->breakpointGCPhysAreaTable = NULL; ++ return true; ++ } ++ return false; ++} ++ ++VMMDECL(int) VMR3PhysSimpleReadGCPhysU(PUVM pUVM, void *pvDst, RTGCPHYS GCPhysSrc, size_t cb) ++{ ++ return PGMPhysSimpleReadGCPhys(pUVM->pVM, pvDst, GCPhysSrc, cb); ++} ++ ++VMMDECL(int) VMR3PhysSimpleWriteGCPhysU(PUVM pUVM, const void *pvBuf, RTGCPHYS GCPhys, size_t cbWrite) ++{ ++ return PGMPhysSimpleWriteGCPhys(pUVM->pVM, GCPhys, pvBuf, cbWrite); ++} ++ ++VMMDECL(int) VMR3SingleStep(PUVM pUVM, PVMCPU pVCpu) ++{ ++ //Dont try to single step on a running ++ if(pVCpu->mystate.s.u8StateBitmap & FDP_STATE_PAUSED){ ++ pVCpu->mystate.s.bSingleStepRequired = true; ++ while(pVCpu->mystate.s.bSingleStepRequired){ ++ //Yield ++ RTThreadSleep(0); ++ } ++ return 0; ++ } ++ return -1; ++} ++ ++VMMDECL(int) VMR3BreakNoLock(PUVM pUVM) ++{ ++ //LogRel(("[WDEBUG] BREAK !\n")); ++ ++ //Wait for all cpu paused ++ for(uint32_t i=0; ipVM->aCpus[i].mystate.s.bPauseRequired = true; ++ ++ //Inject a IPI ++ SUPR3CallVMMR0Ex(pUVM->pVM->pVMR0, pUVM->pVM->aCpus[i].idCpu, VMMR0_DO_GVMM_SCHED_WAKE_UP, 0, NULL); ++ SUPR3CallVMMR0Ex(pUVM->pVM->pVMR0, pUVM->pVM->aCpus[i].idCpu, VMMR0_DO_GVMM_SCHED_POKE, 0, NULL); ++ ++ do{ ++ //LogRel(("[WDEBUG] Waiting for CPU[%d] to pause state %02x\n", i, pUVM->pVM->aCpus[i].mystate.s.u8StateBitmap)); ++ //RTThreadSleep(10); ++ }while(!(pUVM->pVM->aCpus[i].mystate.s.u8StateBitmap & FDP_STATE_PAUSED)); ++ //LogRel(("[WDEBUG] CPU[%d] is paused !\n", i)); ++ } ++ ++ //LogRel(("[WDEBUG] All Cpus are PAUSED !\n")); ++ return 0; ++} ++ ++VMMDECL(int) VMR3Break(PUVM pUVM) ++{ ++ RTSpinlockAcquire(pUVM->pVM->mystate.s.CpuLock); ++ ++ VMR3BreakNoLock(pUVM); ++ ++ RTSpinlockRelease(pUVM->pVM->mystate.s.CpuLock); ++ return 0; ++} ++ ++VMMDECL(int) VMR3ContinueNoWaitNoLock(PUVM pUVM) ++{ ++ //LogRel(("[WDEBUG] VMR3ContinueNoWaitNoLock !\n")); ++ ++ pUVM->pVM->mystate.s.u8StateBitmap &= ~FDP_STATE_DEBUGGER_ALERTED; ++ ++ //Wait for all CPUs resumed ++ for(uint32_t i=0; ipVM->aCpus[i]; ++ uint64_t oldu64TickCount = pVCpu->mystate.s.u64TickCount; ++ pVCpu->mystate.s.bPauseRequired = false; ++ //VMCPU_FF_SET(pVCpu, VMCPU_FF_EXTERNAL_SUSPENDED_MASK); ++ } ++ ++ return 0; ++} ++ ++VMMDECL(int) VMR3ContinueWaitNoLock(PUVM pUVM) ++{ ++ //LogRel(("[WDEBUG] VMR3ContinueWaitNoLock !\n")); ++ ++ pUVM->pVM->mystate.s.u8StateBitmap &= ~FDP_STATE_DEBUGGER_ALERTED; ++ ++ //Wait for all CPUs resumed ++ for(uint32_t i=0; ipVM->aCpus[i]; ++ uint64_t oldu64TickCount = pVCpu->mystate.s.u64TickCount; ++ pVCpu->mystate.s.bPauseRequired = false; ++ //VMCPU_FF_SET(pVCpu, VMCPU_FF_EXTERNAL_SUSPENDED_MASK); ++ while(oldu64TickCount == pVCpu->mystate.s.u64TickCount){ ++ //Yield ++ RTThreadSleep(0); ++ } ++ } ++ ++ return 0; ++} ++ ++VMMDECL(int) VMR3Continue(PUVM pUVM) ++{ ++ RTSpinlockAcquire(pUVM->pVM->mystate.s.CpuLock); ++ ++ VMR3ContinueWaitNoLock(pUVM); ++ ++ RTSpinlockRelease(pUVM->pVM->mystate.s.CpuLock); ++ return 0; ++} ++ ++VMMDECL(uint8_t) VMR3GetFDPState(PUVM pUVM) ++{ ++ uint8_t u8OldState = 0; ++ bool bIsPaused = true; ++ bool bIsBreakpointHitted = false; ++ for(uint32_t i=0; ipVM->aCpus[i].mystate.s.u8StateBitmap & FDP_STATE_PAUSED)){ ++ bIsPaused = false; ++ } ++ //If one CPU hit a breakpoint ++ if(pUVM->pVM->aCpus[i].mystate.s.u8StateBitmap & FDP_STATE_BREAKPOINT_HIT){ ++ bIsBreakpointHitted = true; ++ } ++ } ++ ++ if(bIsPaused){ ++ u8OldState |= FDP_STATE_PAUSED; ++ if(bIsBreakpointHitted){ ++ u8OldState |= FDP_STATE_BREAKPOINT_HIT; ++ } ++ } ++ ++ if(pUVM->pVM->mystate.s.u8StateBitmap & FDP_STATE_DEBUGGER_ALERTED){ ++ u8OldState |= FDP_STATE_DEBUGGER_ALERTED; ++ } ++ if(u8OldState & FDP_STATE_BREAKPOINT_HIT){ ++ pUVM->pVM->mystate.s.u8StateBitmap |= FDP_STATE_DEBUGGER_ALERTED; ++ } ++ return u8OldState; ++} ++ ++VMMDECL(bool) VMR3DisableAllMsrBreakpoint(PVM pVM) ++{ ++ for(int BreakpointId=4*pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointActivated == true ++ && pTempBreakpointEntrie->breakpointType == FDP_MSRHBP){ ++ pTempBreakpointEntrie->breakpointActivated = false; ++ } ++ } ++ return 0; ++} ++ ++VMMDECL(bool) VMR3EnableAllMsrBreakpoint(PVM pVM) ++{ ++ for(int BreakpointId=4*pVM->cCpus; BreakpointIdbp.l[BreakpointId]; ++ if(pTempBreakpointEntrie->breakpointType == FDP_MSRHBP){ ++ pTempBreakpointEntrie->breakpointActivated = true; ++ } ++ } ++ return 0; ++} ++ ++VMMDECL(bool) VMR3HandleSingleStep(PVM pVM, PVMCPU pVCpu) ++{ ++ //Check if Single step is required ! ++ if(pVCpu->mystate.s.bSingleStepRequired){ ++ ++ TMR3NotifyResume(pVM, pVCpu); ++ ++ LogRelDebug(("[WDEBUG] CPU[%d] bSingleStepRequired !\n", pVCpu->idCpu)); ++ ++ //Restore Original Page with Execute right, avoid Breakpoint in SingleStep ++ VMR3RestoreAllOriginalPage(pVM->pUVM, true, true, true); ++ //Disable All Msr Breakpoint, avoid Breakpoint in Breakpoint ++ VMR3DisableAllMsrBreakpoint(pVM); ++ //Disable PageHyperBreapoint ++ DisableAllPageBreakpoint(pVM, pVCpu); ++ //Disable Debug Register ++ uint64_t OldDr7 = CPUMGetGuestDR7(pVCpu); ++ CPUMSetGuestDR7(pVCpu, 0x400); ++ ++ int rc = 0; ++ //First call is for instruction that jump on self "jmp -2 (ebfe)" ++ rc = VBOXSTRICTRC_VAL(EMR3HmSingleInstruction(pVM, pVCpu, 0)); ++ ++ LogRelDebug(("[WDEBUG] CPU[%d] Single Step => %d!\n", pVCpu->idCpu, rc)); ++ ++ if(VM_FF_IS_ANY_SET(pVM, VM_FF_ALL_REM_MASK) ++ || VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_ALL_REM_MASK)){ ++ EMR3ProcessForcedAction(pVM, pVCpu, rc); ++ } ++ ++ //If rc == 0 then it failed, we have to call SingleInstruction whith RIP_CHANGE ++ if(rc == 0){ ++ rc = VBOXSTRICTRC_VAL(EMR3HmSingleInstruction(pVM, pVCpu, EM_ONE_INS_FLAGS_RIP_CHANGE)); ++ ++ LogRelDebug(("[WDEBUG] CPU[%d] Single Step => %d!\n", pVCpu->idCpu, rc)); ++ ++ if(VM_FF_IS_ANY_SET(pVM, VM_FF_ALL_REM_MASK) ++ || VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_ALL_REM_MASK)){ ++ EMR3ProcessForcedAction(pVM, pVCpu, rc); ++ } ++ } ++ ++ //Restore Original Page, avoid VirtualBox being crazy with unknown HCPhys on SoftHyperBreakpoint ++ VMR3RestoreAllOriginalPage(pVM->pUVM, true, true, false); ++ //Enable All Msr Breakpoint ++ VMR3EnableAllMsrBreakpoint(pVM); ++ //Enable All PageHyperBreakpoint ++ EnableAllPageBreakpoint(pVM, pVCpu); ++ //Enable Debug Register ++ CPUMSetGuestDR7(pVCpu, OldDr7); ++ ++ TMR3NotifySuspend(pVM, pVCpu); ++ ++ pVCpu->mystate.s.bSingleStepRequired = false; //Single step no more required ! ++ return true; ++ } ++ return false; ++} ++ ++VMMDECL(int) VMR3InjectInterrupt(PVM pVM, PVMCPU pVCpu, uint32_t enmXcpt, uint32_t uErr, uint64_t Cr2) ++{ ++ return TRPMRaiseXcptErrCR2(pVCpu, NULL, (X86XCPT)enmXcpt, uErr, Cr2); ++} ++ ++#include ++ ++VMMDECL(int) VMR3ClearInterrupt(PUVM pUVM, PVMCPU pVCpu) ++{ ++ //return PDMR3UsbHasHub(pUVM); ++ PDMR3PowerOn(pUVM->pVM); ++ return 0; ++} ++ ++VMMDECL(void) VMR3SetFDPShm(PUVM pUVM, void *pFdpShm) ++{ ++ pUVM->pVM->mystate.s.pFdpShm = pFdpShm; ++} ++ ++VMMDECL(bool) VMR3EnterPause(PVM pVM, PVMCPU pVCpu) ++{ ++ if(pVCpu->idCpu == 0){ ++ //Update FDP_CPU_CTX ++ VMR3UpdateFdpCpuCtx(pVCpu); ++ TMR3NotifySuspend(pVM, pVCpu); ++ ++ //Active wait ++ uint32_t u32WaitCount = 0; ++ while(pVCpu->mystate.s.bPauseRequired == true){ ++ if(VMR3HandleSingleStep(pVM, pVCpu) == true){ ++ //Update FDP_CPU_CTX ++ VMR3UpdateFdpCpuCtx(pVCpu); ++ u32WaitCount = 0; ++ } ++ //Powersaving :) ++ if((u32WaitCount & 0xFFFFFF) == 0xFFFFFF){ ++ RTThreadSleep(5); ++ }else{ ++ u32WaitCount++; ++ } ++ } ++ ++ TMR3NotifyResume(pVM, pVCpu); ++ ++ //ProcessForcedAction avoid freeze in CLI...BP...SAVE...STI ++ if(VM_FF_IS_ANY_SET(pVM, VM_FF_ALL_REM_MASK) ++ || VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_ALL_REM_MASK)){ ++ EMR3ProcessForcedAction(pVM, pVCpu, 0); ++ } ++ ++ ++ //Update FDP_CPU_CTX ++ VMR3UpdateFdpCpuCtx(pVCpu); ++ } ++ return true; ++} ++ ++#include "VMMInternal.h" ++ ++ ++#define DR0_ENABLED 0x3 ++#define DR1_ENABLED 0xC ++#define DR2_ENABLED 0x30 ++#define DR3_ENABLED 0xC0 ++ ++#define DR_READ 0x03 ++#define DR_WRITE 0x01 ++#define DR_EXECUTE 0x00 ++ ++/* ++* @brief Convert a Debug Register Breakpoint Type to FDP Breakpoint Type ++* ++*/ ++int GetDrType(uint64_t DrType) ++{ ++ switch(DrType){ ++ case DR_READ: ++ return FDP_READ_BP; ++ case DR_WRITE: ++ return FDP_WRITE_BP; ++ case DR_EXECUTE: ++ return FDP_EXECUTE_BP; ++ } ++ return 0; ++} ++ ++/* ++ * @brief Get the Breakpoint Lenght from Debug Register Breakpoint Lenght ++ */ ++uint64_t GetDrLength(uint64_t DrLength) ++{ ++ switch(DrLength){ ++ case 0: ++ return 1; ++ case 1: ++ return 2; ++ case 2: ++ return 8; ++ case 3: ++ return 4; ++ } ++ return 1; ++} ++ ++VMMR3DECL(uint32_t) VMR3GetCPUCount(PUVM pUVM) ++{ ++ return pUVM->pVM->cCpus; ++} ++ ++ + + /** + * Halted VM Wait. +@@ -1118,6 +2068,121 @@ VMMR3_INT_DECL(void) VMR3NotifyCpuFFU(PUVMCPU pUVCpu, uint32_t fFlags) + */ + VMMR3_INT_DECL(int) VMR3WaitHalted(PVM pVM, PVMCPU pVCpu, bool fIgnoreInterrupts) + { ++ /*MYCODE*/ ++ LogRel(("[WDEBUG] VMR3WaitHalted\n")); ++ if(pVCpu->mystate.s.bInstallDrBreakpointRequired){ ++ LogRelDebug(("[WDEBUG] CPU[%d] Entering bInstallDrBreakpointRequired !!\n", pVCpu->idCpu)); ++ pVCpu->mystate.s.u8StateBitmap |= FDP_STATE_PAUSED; ++ ++ //Break all CPUs ++ VMR3Break(pVM->pUVM); ++ ++ //Remove all breakpoint ++ int BreakpointId = 0; ++ for(int BreakpointId = (0+(pVCpu->idCpu*4)); BreakpointId<(int)(4+(pVCpu->idCpu*4)); BreakpointId++){ ++ VMR3RemoveBreakpoint(pVM->pUVM, BreakpointId); ++ } ++ ++ //Install all breakpoint ++ LogRelDebug(("[WDEBUG] CPU[%d] aGuestDr[0] %p\n", pVCpu->idCpu, pVCpu->mystate.s.aGuestDr[0])); ++ LogRelDebug(("[WDEBUG] CPU[%d] aGuestDr[1] %p\n", pVCpu->idCpu, pVCpu->mystate.s.aGuestDr[1])); ++ LogRelDebug(("[WDEBUG] CPU[%d] aGuestDr[2] %p\n", pVCpu->idCpu, pVCpu->mystate.s.aGuestDr[2])); ++ LogRelDebug(("[WDEBUG] CPU[%d] aGuestDr[3] %p\n", pVCpu->idCpu, pVCpu->mystate.s.aGuestDr[3])); ++ LogRelDebug(("[WDEBUG] CPU[%d] aGuestDr[7] %p\n", pVCpu->idCpu, pVCpu->mystate.s.aGuestDr[7])); ++ ++ //Update Guest Breakpoint ++ for(uint8_t i=0; i<4; i++){ ++ if(pVCpu->mystate.s.aGuestDr[7] & (0x3<< (i*2))){ ++ uint8_t TEMP_DRX_LENGTH = (pVCpu->mystate.s.aGuestDr[7] & (0x3 << (18+i*4))) >> (18+i*4); ++ uint8_t DRX_LENGTH = GetDrLength(TEMP_DRX_LENGTH); ++ uint8_t TEMP_DRX_TYPE = (pVCpu->mystate.s.aGuestDr[7] & (0x3 << (16+i*4))) >> (16+i*4); ++ int DRX_TYPE = GetDrType(TEMP_DRX_TYPE); ++ ++ int BreakpointId = -1; ++ if(DRX_TYPE > 0){ ++ BreakpointId = VMR3AddPageBreakpoint(pVM->pUVM, pVCpu, i+(pVCpu->idCpu*4), DRX_TYPE, FDP_VIRTUAL_ADDRESS, pVCpu->mystate.s.aGuestDr[i], DRX_LENGTH); ++ } ++ //LogRel(("INSTALL DR[%d] %d\n", i, BreakpointId)); ++ } ++ } ++ ++ ++ pVCpu->mystate.s.u8StateBitmap &= ~FDP_STATE_PAUSED; ++ pVCpu->mystate.s.bPauseRequired = false; ++ pVCpu->mystate.s.bInstallDrBreakpointRequired = false; ++ ++ //Continue all CPUs ++ VMR3ContinueNoWaitNoLock(pVM->pUVM); ++ ++ LogRelDebug(("[WDEBUG] CPU[%d] Leaving bInstallDrBreakpointRequired !!\n", pVCpu->idCpu)); ++ return VINF_EM_RESCHEDULE; ++ } ++ ++ if(pVCpu->mystate.s.bHardHyperBreakPointHitted ++ || pVCpu->mystate.s.bPageHyperBreakPointHitted ++ || pVCpu->mystate.s.bSoftHyperBreakPointHitted ++ || pVCpu->mystate.s.bMsrHyperBreakPointHitted ++ || pVCpu->mystate.s.bCrHyperBreakPointHitted){ ++ ++ //Update FDP_CPU_CTX ++ VMR3UpdateFdpCpuCtx(pVCpu); ++ ++ if(pVCpu->mystate.s.bPageHyperBreakPointHitted){ ++ LogRel(("[WDEBUG] CPU[%d] bPageHyperBreakPointHitted !!\n", pVCpu->idCpu)); ++ } ++ if(pVCpu->mystate.s.bSoftHyperBreakPointHitted){ ++ LogRel(("[WDEBUG] CPU[%d] bSoftHyperBreakPointHitted !!\n", pVCpu->idCpu)); ++ } ++ if(pVCpu->mystate.s.bHardHyperBreakPointHitted){ ++ LogRel(("[WDEBUG] CPU[%d] bHardHyperBreakPointHitted !!\n", pVCpu->idCpu)); ++ pVCpu->mystate.s.u8StateBitmap |= FDP_STATE_HARD_BREAKPOINT_HIT; ++ } ++ if(pVCpu->mystate.s.bMsrHyperBreakPointHitted){ ++ LogRel(("[WDEBUG] CPU[%d] bMsrHyperBreakPointHitted !!\n", pVCpu->idCpu)); ++ } ++ if(pVCpu->mystate.s.bCrHyperBreakPointHitted){ ++ LogRel(("[WDEBUG] CPU[%d] bCrHyperBreakPointHitted !!\n", pVCpu->idCpu)); ++ } ++ ++ ++ //Restore OriginalPage for all SoftHyperBreakpoint ++ VMR3RestoreAllOriginalPage(pVM->pUVM, true, true, false); ++ ++ //Set the CPU as PAUSED and BREAKPOINT_HITTED ++ pVCpu->mystate.s.u8StateBitmap |= FDP_STATE_BREAKPOINT_HIT; ++ pVCpu->mystate.s.u8StateBitmap |= FDP_STATE_PAUSED; ++ ++ //Break all CPUs ++ VMR3Break(pVM->pUVM); ++ ++ //TODO: Protect this ! ++ FDP_SHM *pFdpShm = (FDP_SHM *)pVM->mystate.s.pFdpShm; ++ FDP_SetStateChanged(pFdpShm); ++ ++ //Waiting for debugger resume ! ++ VMR3EnterPause(pVM, pVCpu); ++ ++ bool bMsrHyperBreakpointHitted = pVCpu->mystate.s.bMsrHyperBreakPointHitted; ++ ++ //We are ready to go ! ++ pVCpu->mystate.s.bHardHyperBreakPointHitted = false; ++ pVCpu->mystate.s.bPageHyperBreakPointHitted = false; ++ pVCpu->mystate.s.bSoftHyperBreakPointHitted = false; ++ pVCpu->mystate.s.bMsrHyperBreakPointHitted = false; ++ pVCpu->mystate.s.bCrHyperBreakPointHitted = false; ++ pVCpu->mystate.s.u8StateBitmap = 0; ++ ++ //Single step for MsrBreakpoint... Maybe this stuff should be done in Winbagility... ++ if(bMsrHyperBreakpointHitted == true){ ++ pVCpu->mystate.s.bSingleStepRequired = true; ++ VMR3HandleSingleStep(pVM, pVCpu); ++ pVCpu->mystate.s.bSingleStepRequired = false; ++ } ++ ++ LogRel(("[WDEBUG] CPU[%d] Leaving Breakpoint !\n", pVCpu->idCpu)); ++ return VINF_EM_RESCHEDULE; ++ } ++ /*ENDMYCODE*/ + LogFlow(("VMR3WaitHalted: fIgnoreInterrupts=%d\n", fIgnoreInterrupts)); + + /* +diff --git a/src/VBox/VMM/VMMR3/VMM.cpp b/src/VBox/VMM/VMMR3/VMM.cpp +index aebfdac8..f5b7e0a3 100644 +--- a/src/VBox/VMM/VMMR3/VMM.cpp ++++ b/src/VBox/VMM/VMMR3/VMM.cpp +@@ -1503,6 +1503,10 @@ VMMR3_INT_DECL(int) VMMR3HmRunGC(PVM pVM, PVMCPU pVCpu) + if (RT_LIKELY(rc == VINF_SUCCESS)) + rc = pVCpu->vmm.s.iLastGZRc; + #endif ++ /*MYCODE*/ ++ if(pVCpu->mystate.s.bPauseRequired == true) ++ break; ++ /*ENDMYCODE*/ + } while (rc == VINF_EM_RAW_INTERRUPT_HYPER); + + #if 0 /** @todo triggers too often */ +diff --git a/src/VBox/VMM/include/PGMInternal.h b/src/VBox/VMM/include/PGMInternal.h +index 2f4778e8..17cef1a6 100644 +--- a/src/VBox/VMM/include/PGMInternal.h ++++ b/src/VBox/VMM/include/PGMInternal.h +@@ -99,6 +99,9 @@ + #if (HC_ARCH_BITS == 64) && !defined(IN_RC) + # define PGM_WITH_LARGE_PAGES + #endif ++/*MYCODE*/ ++#undef PGM_WITH_LARGE_PAGES ++/*ENDMYENCODE*/ + + /** + * Enables optimizations for MMIO handlers that exploits X86_TRAP_PF_RSVD and diff --git a/KDKutils/1-create-DWARF.sh b/KDKutils/1-create-DWARF.sh new file mode 100755 index 00000000..0e2d95d1 --- /dev/null +++ b/KDKutils/1-create-DWARF.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -e +dirname () { python -c "import os; print(os.path.dirname(os.path.realpath('$0')))"; } +cd "$(dirname "$0")" + +: ${1?"Usage: $0 VARSFILE"} +VARSFILE="$1" +echo "VARSFILE=\"$VARSFILE\"" + +source "$VARSFILE" + +echo "KDKUTILS_SOURCE_KERNEL_DWARF=\"$KDKUTILS_SOURCE_KERNEL_DWARF\"" +echo "KDKUTILS_SOURCE_KERNEL_DIEOFFSETS=\"${KDKUTILS_SOURCE_KERNEL_DIEOFFSETS[@]}\"" +echo "KDKUTILS_GENERATED_KERNEL=\"$KDKUTILS_GENERATED_KERNEL\"" + +# from the input DWARF file, extract the structures/variables at the specified offsets +DWARFUTILS_SRCDIRECTORY=$(../DWARFutils/parse-dwarf-types-to-c-source.py "$KDKUTILS_SOURCE_KERNEL_DWARF" ${KDKUTILS_SOURCE_KERNEL_DIEOFFSETS[@]} \ + | python -c 'import re, sys; print(re.search("Output directory: .(.+?).$", sys.stdin.read()).group(1))' ) +# compile the extracted structures/variables into a new DWARF file +cd "$DWARFUTILS_SRCDIRECTORY" >/dev/null + command -v clang-format >/dev/null && clang-format -i -style="{AlignConsecutiveDeclarations: true}" *.c + clang -g -x c -shared *.c + mkdir -p "$(dirname "$KDKUTILS_GENERATED_KERNEL")" + cp "a.out.dSYM/Contents/Resources/DWARF/a.out" "$KDKUTILS_GENERATED_KERNEL" + rm -r "a.out" "a.out.dSYM" + file "$KDKUTILS_GENERATED_KERNEL" +cd - >/dev/null diff --git a/KDKutils/2-fake-DWARF.sh b/KDKutils/2-fake-DWARF.sh new file mode 100755 index 00000000..a199a5e3 --- /dev/null +++ b/KDKutils/2-fake-DWARF.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -e +dirname () { python -c "import os; print(os.path.dirname(os.path.realpath('$0')))"; } +cd "$(dirname "$0")" + +: ${1?"Usage: $0 VARSFILE"} +VARSFILE="$1" +echo "VARSFILE=\"$VARSFILE\"" + +source "$VARSFILE" + +echo "KDKUTILS_TARGET_KERNEL=\"$KDKUTILS_TARGET_KERNEL\"" +echo "KDKUTILS_TARGET_KERNEL_DWARF=\"$KDKUTILS_TARGET_KERNEL_DWARF\"" +echo "KDKUTILS_RELOCATESYMBOLS=\"$KDKUTILS_RELOCATESYMBOLS\"" + +# update the UUID of the generated DWARF so that it matches the UUID of the kernel to debug +DEBUGGEEKERNEL_UUID=$(dwarfdump -u "$KDKUTILS_TARGET_KERNEL" | python -c 'import re, sys; print(re.match(r"UUID: (.+?) ", sys.stdin.read()).group(1))') +./set-macho-uuid.py "$KDKUTILS_TARGET_KERNEL_DWARF" "$DEBUGGEEKERNEL_UUID" + +# relocate the "__TEXT", "__DATA" and "__LINKEDIT" segments of the generated DWARF so that +# their location matches the location of the same segments of the kernel to debug +vmaddr () { + SEGNAME="$1" + otool -l "$KDKUTILS_TARGET_KERNEL" | grep -A2 "segname $SEGNAME" | head -n 3 | python -c 'import re, sys; print(re.search(r"vmaddr (0x[0-9a-f]+)", sys.stdin.read()).group(1))' +} +vmsize () { + SEGNAME="$1" + otool -l "$KDKUTILS_TARGET_KERNEL" | grep -A2 "segname $SEGNAME" | head -n 3 | python -c 'import re, sys; print(re.search(r"vmsize (0x[0-9a-f]+)", sys.stdin.read()).group(1))' +} +./set-segments-vmaddr-and-vmsize.py "$KDKUTILS_TARGET_KERNEL_DWARF" \ + --text "$(vmaddr __TEXT),$(vmsize __TEXT)" \ + --data "$(vmaddr __DATA),$(vmsize __DATA)" \ + --linkedit "$(vmaddr __LINKEDIT),$(vmsize __LINKEDIT)" + +# relocate the symbols in the generated DWARF so that their location matches the location +# of the same symbols in the symbol table of the kernel to debug +relocate () { + SYMBOL="$1" + ADDRESS=$(nm "$KDKUTILS_TARGET_KERNEL" \ + | grep -E " _$SYMBOL\$" \ + | echo "0x$(awk '{print $1;}')") + ../DWARFutils/relocate-dwarf-variable.py "$KDKUTILS_TARGET_KERNEL_DWARF" "$SYMBOL" "$ADDRESS" +} +for SYMBOL in "${KDKUTILS_RELOCATESYMBOLS[@]}" +do + relocate "$SYMBOL" +done diff --git a/KDKutils/3-attach-DWARF.sh b/KDKutils/3-attach-DWARF.sh new file mode 100755 index 00000000..d72535c4 --- /dev/null +++ b/KDKutils/3-attach-DWARF.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -e +dirname () { python -c "import os; print(os.path.dirname(os.path.realpath('$0')))"; } +cd "$(dirname "$0")" + +: ${1?"Usage: $0 VARSFILE"} +VARSFILE="$1" +echo "VARSFILE=\"$VARSFILE\"" + +source "$VARSFILE" + +echo "KDKUTILS_TARGET_KERNEL=\"$KDKUTILS_TARGET_KERNEL\"" +echo "KDKUTILS_TARGET_KERNEL_DWARF=\"$KDKUTILS_TARGET_KERNEL_DWARF\"" +echo "KDKUTILS_LLDBMACROS=\"$KDKUTILS_LLDBMACROS\"" +echo "LLDBAGILITY_VMNAME=\"$LLDBAGILITY_VMNAME\"" + +# attach and debug the VM +env PATH="/usr/bin:/bin:/usr/sbin:/sbin" LOGLEVEL="DEBUG" lldb \ + -o "target create \"$KDKUTILS_TARGET_KERNEL\"" \ + -o "target symbols add \"$KDKUTILS_TARGET_KERNEL_DWARF\"" \ + -o "fdp-attach $LLDBAGILITY_VMNAME" \ + -o "command script import \"$KDKUTILS_LLDBMACROS\"" \ + -o "showversion" diff --git a/KDKutils/README.md b/KDKutils/README.md new file mode 100644 index 00000000..20c53c72 --- /dev/null +++ b/KDKutils/README.md @@ -0,0 +1,75 @@ +# KDKutils +This folder contains some utility scripts for creating a fake kernel DWARF file to use available XNU's lldbmacros with a macOS kernel without its specific Kernel Debug Kit (KDK). Some of the examples included here uses the `data.zip` archive uploaded in the Releases section. + +## Files + +### `./set-macho-uuid.py` +``` +usage: set-macho-uuid.py [-h] machofile uuid +``` +This script allows to replace the UUID in a Mach-O file with another arbitrary UUID. + +### `./set-segments-vmaddr-and-vmsize.py` +``` +usage: set-segments-vmaddr-and-vmsize.py [-h] [--text TEXT] [--data DATA] + [--linkedit LINKEDIT] + machofile +``` +This script allows to modify the virtual address and size of the `__TEXT`, `__DATA` and `__LINKEDIT` segments in a Mach-O file. + +### `./1-create-DWARF.sh` +``` +Usage: ./1-create-DWARF.sh VARSFILE +``` +This script extracts from a source DWARF file the structures/variables at the specified offsets and recompiles the generated C sources to create a new DWARF. The file `VARSFILE` should define the shell variables: +- `KDKUTILS_SOURCE_KERNEL_DWARF`: path to the source kernel DWARF file +- `KDKUTILS_SOURCE_KERNEL_DIEOFFSETS`: array of DIE offsets to extract (e.g. offsets of all symbols used by lldbmacros) +- `KDKUTILS_GENERATED_KERNEL`: path of the output DWARF file + +### `./2-fake-DWARF.sh` +``` +Usage: ./2-fake-DWARF.sh VARSFILE +``` +This script modifies the created DWARF file so that it can be used as a fake symbol file for the specified macOS kernel. The file `VARSFILE` should define the shell variables: +- `KDKUTILS_TARGET_KERNEL`: path to the macOS kernel binary (e.g. the one used by the debuggee) +- `KDKUTILS_TARGET_KERNEL_DWARF`: path to the DWARF file created with `./1-create-DWARF.sh` (e.g. same as `KDKUTILS_GENERATED_KERNEL`) +- `KDKUTILS_RELOCATESYMBOLS`: array of DIE names to be relocated (e.g. names of all symbols used by lldbmacros) + +### `./3-attach-DWARF.sh` +``` +Usage: ./3-attach-DWARF.sh VARSFILE +``` +Assuming LLDBagility has been set up, this script simply starts LLDB with the specified target and symbol file, then attaches to the specified macOS VM and finally imports the specified LLDBmacros. The file `VARSFILE` should define the shell variables: +- `KDKUTILS_TARGET_KERNEL`: as above +- `KDKUTILS_TARGET_KERNEL_DWARF`: as above +- `KDKUTILS_LLDBMACROS`: path to `kernel.py` from the lldbmacros to use +- `LLDBAGILITY_VMNAME`: name of the VM to attach to with LLDBagility + +## Requisites +- A recent Python 2 or Python 3 interpreter +- A [macOS KDK](https://developer.apple.com/download/more/?q=Kernel%20Debug%20Kit) (preferably for a similar build of the kernel used by the debuggee), containing the source kernel DWARF file (e.g. `/Library/Developer/KDKs/KDK_10.14.4_18E226.kdk/System/Library/Kernels/kernel.dSYM/Contents/Resources/DWARF/kernel`) and the accompanying lldbmacros (e.g. `/Library/Developer/KDKs/KDK_10.14.4_18E226.kdk/System/Library/Kernels/kernel.dSYM/Contents/Resources/Python/`) + +## Usage +In the `examples/` directory, two complete input files are provided with the necessary definitions for using lldbmacros from macOS 10.14.2 build 18C54 with macOS 10.14.3 build 18D109 or macOS 10.14.4 build 18E226; use them as a starting point for creating input files for other builds. The examples uses the `data.zip` archive uploaded in the Releases section. + +Once the input file is filled with appropriate values for all the required variables (as in the two provided examples): +1. Execute `./1-create-DWARF.sh ` to create a new DWARF; +2. then, execute `./2-fake-DWARF.sh ` to update the created DWARF; +3. finally, if LLDBagility is set up, execute `./3-attach-DWARF.sh ` to start LLDB and attach to the VM with the symbols provided by the created DWARF. + +#### Which are correct values for `KDKUTILS_SOURCE_KERNEL_DIEOFFSETS`? +To work correctly, lldbmacros rely on a few structures/variables from the kernel binary, like `version`, `kernel_stack_size`, `kdp` and so on; because of this, the created DWARF file must naturally contain at minimum all of them. For each structure/variable used by lldbmacros (see the provided examples for a list), it is possible to find its offset in the source DWARF file with a command similar to: +``` +$ dwarfdump -n kdp /Library/Developer/KDKs/KDK_10.12_16A323.kdk/System/Library/Kernels/kernel.dSYM/Contents/Resources/DWARF/kernel | grep -C6 ffffff +0x0002c6d8: TAG_variable [54] + AT_name( "kdp" ) + AT_type( {0x0002c6ee} ( kdp_glob_t ) ) + AT_external( 0x01 ) + AT_decl_file( "/Library/Caches/com.apple.xbs/Sources/xnu/xnu-3789.1.32/osfmk/kdp/kdp.c" ) + AT_decl_line( 99 ) + AT_location( [0xffffff8000a63008] ) +``` +In this example, the DIE offset of the `kdp` variable in the source DWARF file is 0x0002c6d8. + +#### Which are correct values for `KDKUTILS_RELOCATESYMBOLS`? +This is simply the list of the names of all symbols dumped from the source DWARF file, i.e. `KDKUTILS_SOURCE_KERNEL_DIEOFFSETS`. diff --git a/KDKutils/examples/10-14-3-18D109-from-10-14-2-18C54.vars b/KDKutils/examples/10-14-3-18D109-from-10-14-2-18C54.vars new file mode 100755 index 00000000..4e5b0d6e --- /dev/null +++ b/KDKutils/examples/10-14-3-18D109-from-10-14-2-18C54.vars @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +KDKUTILS_SOURCE_KERNEL_DWARF="../data/10-14-2-18C54/DWARF/kernel" +KDKUTILS_SOURCE_KERNEL_DIEOFFSETS=( + `#FindGlobalVariables` \ + 0x01478819 `#version` \ + 0x001a4241 `#kernel_stack_size` \ + 0x00029b5a `#kdp` \ + 0x001793d9 `#threads_count` \ + 0x00179419 `#processor_list` \ + 0x001793c4 `#threads` \ + `#kern.globals` \ + 0x00dcd769 `#initproc` \ +) +KDKUTILS_GENERATED_KERNEL="/tmp/kernel" + +KDKUTILS_TARGET_KERNEL="../data/10-14-3-18D109/kernel" +KDKUTILS_TARGET_KERNEL_DWARF="$KDKUTILS_GENERATED_KERNEL" +KDKUTILS_RELOCATESYMBOLS=( + `#FindGlobalVariables` \ + "version" \ + "kernel_stack_size" \ + "kdp" \ + "threads_count" \ + "processor_list" \ + "threads" \ + `#kern.globals` \ + "initproc" \ +) + +KDKUTILS_LLDBMACROS="../data/10-14-2-18C54/Python/kernel.py" +LLDBAGILITY_VMNAME="macos-mojave-18D109" diff --git a/KDKutils/examples/10-14-4-18E226-from-10-14-2-18C54.vars b/KDKutils/examples/10-14-4-18E226-from-10-14-2-18C54.vars new file mode 100755 index 00000000..5ae67d02 --- /dev/null +++ b/KDKutils/examples/10-14-4-18E226-from-10-14-2-18C54.vars @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +KDKUTILS_SOURCE_KERNEL_DWARF="../data/10-14-2-18C54/DWARF/kernel" +KDKUTILS_SOURCE_KERNEL_DIEOFFSETS=( + `#FindGlobalVariables` \ + 0x01478819 `#version` \ + 0x001a4241 `#kernel_stack_size` \ + 0x00029b5a `#kdp` \ + 0x001793d9 `#threads_count` \ + 0x00179419 `#processor_list` \ + 0x001793c4 `#threads` \ + `#kern.globals` \ + 0x00dcd769 `#initproc` \ +) +KDKUTILS_GENERATED_KERNEL="/tmp/kernel" + +KDKUTILS_TARGET_KERNEL="../data/10-14-4-18E226/kernel" +KDKUTILS_TARGET_KERNEL_DWARF="$KDKUTILS_GENERATED_KERNEL" +KDKUTILS_RELOCATESYMBOLS=( + `#FindGlobalVariables` \ + "version" \ + "kernel_stack_size" \ + "kdp" \ + "threads_count" \ + "processor_list" \ + "threads" \ + `#kern.globals` \ + "initproc" \ +) + +KDKUTILS_LLDBMACROS="../data/10-14-2-18C54/Python/kernel.py" +LLDBAGILITY_VMNAME="macos-mojave-18E226" diff --git a/KDKutils/kdkutils.py b/KDKutils/kdkutils.py new file mode 100644 index 00000000..06bc1f4b --- /dev/null +++ b/KDKutils/kdkutils.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +import ctypes +import struct + + +def p64(i): + return struct.pack("/KDPutils`; +- then execute `python -m pip install .` (on macOS, execute either `sudo /usr/bin/python setup.py install` to install KDPutils for system Python or `/usr/local/bin/python -m pip install .` for Homebrew Python). + +## Usage +To run the included example: +1. set up a macOS machine for remote kernel debugging; +2. then, boot the machine, drop in the debugger (by e.g. injecting an NMI) and wait until the string "`Waiting for remote debugger connection.`" is printed on screen; +3. finally, execute `python /KDPutils/examples/kdpclient.py ` to connect to the debuggee (via Ethernet only) and retrieve the kernel version. diff --git a/KDPutils/examples/kdpclient.py b/KDPutils/examples/kdpclient.py new file mode 100755 index 00000000..64001251 --- /dev/null +++ b/KDPutils/examples/kdpclient.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +import argparse +import socket + +import kdputils.protocol +import kdputils.requests +from kdputils.protocol import KDPRequest + + +class KDPClient: + def __init__(self, kdpserver_host): + self.sock_reply = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock_reply.bind(("0.0.0.0", 0)) + _, self.req_reply_port = self.sock_reply.getsockname() + + self.sock_exc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock_exc.bind(("0.0.0.0", 0)) + _, self.exc_note_port = self.sock_exc.getsockname() + + self.kdpserver_host = kdpserver_host + self.seqid = 0 + self.sesskey = 0x1337 + + def __enter__(self): + self._reattach() + self._connect() + return self + + def __exit__(self, *exc): + self._reattach() + + def send_req_and_recv_reply(self, reqpkt): + kdputils.protocol.send( + self.sock_reply, + (self.kdpserver_host, kdputils.protocol.KDP_REMOTE_PORT), + reqpkt, + self.seqid, + self.sesskey, + ) + replypkt, _ = kdputils.protocol.recv(self.sock_reply) + return replypkt + + def _reattach(self): + self.seqid = 0 + replypkt = self.send_req_and_recv_reply( + kdputils.requests.kdp_reattach(self.req_reply_port) + ) + assert replypkt["is_reply"] and replypkt["request"] == KDPRequest.KDP_REATTACH + + def _connect(self): + replypkt = self.send_req_and_recv_reply( + kdputils.requests.kdp_connect( + self.req_reply_port, self.exc_note_port, b"> 7 + pkt["request"] = pkt["type"] & 0b01111111 + del pkt["type"] + for name, ttype in BODY_FIELDS[(pkt["is_reply"], pkt["request"])]: + pkt[name], size = ttype.unpack(data[offset:]) + offset += size + return pkt + + +def calcsize(pkt): + pkt["type"] = pkt["is_reply"] << 7 | pkt["request"] & 0b01111111 + size = sum( + ttype.calcsize(pkt[name]) + for fields in (HEADER_FIELDS, BODY_FIELDS[(pkt["is_reply"], pkt["request"])]) + for name, ttype in fields + ) + del pkt["type"] + return size + + +def send(from_sock, to_addr, sendpkt, seq, key): + sendpkt["seq"] = seq + sendpkt["len"] = calcsize(sendpkt) + sendpkt["key"] = key + logger.debug("--> {}".format(_summary(sendpkt))) + return from_sock.sendto(pack(sendpkt), to_addr) + + +def recv(from_sock): + data, addr = from_sock.recvfrom(MAX_KDP_PKT_SIZE) + recvpkt = unpack(data) + logger.debug("<-- {}".format(_summary(recvpkt))) + return recvpkt, addr + + +KDPRequest.names = { + getattr(KDPRequest, name): name + for name in dir(KDPRequest) + if name.startswith("KDP_") +} + +KDPError.names = { + getattr(KDPError, name): name for name in dir(KDPError) if name.startswith("KDPERR") +} + + +def _repr(k, v): + if k == "request": + return KDPRequest.names[v] + elif k == "error": + return KDPError.names[v] + elif isinstance(v, (int, long)): + return "0x{:x}".format(v) + return repr(v) + + +def _summary(pkt): + return "{} {{{}}}".format( + KDPRequest.names[pkt["request"]], + ", ".join( + "'{}': {}".format(k, _repr(k, v)) for k, v in pkt.items() if k != "request" + ), + ) + + +if __name__ == "__main__": + import unittest + from . import replies + + class PackageTestCase(unittest.TestCase): + def test_pack_unpack(self): + pkt1 = replies.kdp_connect(error=123) + pkt1["seq"] = 1 + pkt1["len"] = 2 + pkt1["key"] = 3 + pkt2 = unpack(pack(pkt1)) + self.assertTrue(all(pkt1[k] == pkt2[k] for k in pkt1)) + + unittest.main() diff --git a/KDPutils/kdputils/replies.py b/KDPutils/kdputils/replies.py new file mode 100644 index 00000000..8fea2f34 --- /dev/null +++ b/KDPutils/kdputils/replies.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +from . import protocol + +# https://github.com/apple/darwin-xnu/blob/xnu-4903.221.2/osfmk/kdp/kdp.c + + +def kdp_connect(error): + return dict( + is_reply=0x1, + request=protocol.KDPRequest.KDP_CONNECT, + seq=-1, + len=-1, + key=-1, + error=error, + ) + + +def kdp_disconnect(): + return dict( + is_reply=0x1, request=protocol.KDPRequest.KDP_DISCONNECT, seq=-1, len=-1, key=-1 + ) + + +def kdp_hostinfo(cpus_mask, cpu_type, cpu_subtype): + return dict( + is_reply=0x1, + request=protocol.KDPRequest.KDP_HOSTINFO, + seq=-1, + len=-1, + key=-1, + cpus_mask=cpus_mask, + cpu_type=cpu_type, + cpu_subtype=cpu_subtype, + ) + + +def kdp_version(version, feature): + return dict( + is_reply=0x1, + request=protocol.KDPRequest.KDP_VERSION, + seq=-1, + len=-1, + key=-1, + version=version, + feature=feature, + pad0=0x0, + pad1=0x0, + ) + + +def kdp_readregs(error, regs): + return dict( + is_reply=0x1, + request=protocol.KDPRequest.KDP_READREGS, + seq=-1, + len=-1, + key=-1, + error=error, + **regs + ) + + +def kdp_writeregs(error): + return dict( + is_reply=0x1, + request=protocol.KDPRequest.KDP_WRITEREGS, + seq=-1, + len=-1, + key=-1, + error=error, + ) + + +def kdp_resumecpus(): + return dict( + is_reply=0x1, request=protocol.KDPRequest.KDP_RESUMECPUS, seq=-1, len=-1, key=-1 + ) + + +def kdp_reattach(): + return dict( + is_reply=0x1, request=protocol.KDPRequest.KDP_REATTACH, seq=-1, len=-1, key=-1 + ) + + +def kdp_readmem64(error, data): + return dict( + is_reply=0x1, + request=protocol.KDPRequest.KDP_READMEM64, + seq=-1, + len=-1, + key=-1, + error=error, + data=data, + ) + + +def kdp_writemem64(error): + return dict( + is_reply=0x1, + request=protocol.KDPRequest.KDP_WRITEMEM64, + seq=-1, + len=-1, + key=-1, + error=error, + ) + + +def kdp_breakpoint64_set(error): + return dict( + is_reply=0x1, + request=protocol.KDPRequest.KDP_BREAKPOINT64_SET, + seq=-1, + len=-1, + key=-1, + error=error, + ) + + +def kdp_breakpoint64_remove(error): + return dict( + is_reply=0x1, + request=protocol.KDPRequest.KDP_BREAKPOINT64_REMOVE, + seq=-1, + len=-1, + key=-1, + error=error, + ) + + +def kdp_kernelversion(version): + return dict( + is_reply=0x1, + request=protocol.KDPRequest.KDP_KERNELVERSION, + seq=-1, + len=-1, + key=-1, + version=version, + ) diff --git a/KDPutils/kdputils/requests.py b/KDPutils/kdputils/requests.py new file mode 100644 index 00000000..a34de49c --- /dev/null +++ b/KDPutils/kdputils/requests.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +from . import protocol + +# https://github.com/llvm/llvm-project/blob/llvmorg-8.0.0/lldb/source/Plugins/Process/MacOSX-Kernel/CommunicationKDP.cpp +# https://github.com/apple/darwin-xnu/blob/xnu-4903.221.2/osfmk/kdp/ml/x86_64/kdp_machdep.c#L72 + + +def kdp_connect(req_reply_port, exc_note_port, greeting): + return dict( + is_reply=0x0, + request=protocol.KDPRequest.KDP_CONNECT, + seq=-1, + len=-1, + key=-1, + req_reply_port=req_reply_port, + exc_note_port=exc_note_port, + greeting=greeting, + ) + + +def kdp_reattach(req_reply_port): + return dict( + is_reply=0x0, + request=protocol.KDPRequest.KDP_REATTACH, + seq=-1, + len=-1, + key=-1, + req_reply_port=req_reply_port, + ) + + +def kdp_kernelversion(): + return dict( + is_reply=0x0, + request=protocol.KDPRequest.KDP_KERNELVERSION, + seq=-1, + len=-1, + key=-1, + ) + + +def kdp_exception(n_exc_info, cpu, exception, code, subcode): + return dict( + is_reply=0x0, + request=protocol.KDPRequest.KDP_EXCEPTION, + seq=-1, + len=-1, + key=-1, + n_exc_info=n_exc_info, + cpu=cpu, + exception=exception, + code=code, + subcode=subcode, + ) diff --git a/KDPutils/setup.py b/KDPutils/setup.py new file mode 100644 index 00000000..d7c2456c --- /dev/null +++ b/KDPutils/setup.py @@ -0,0 +1,3 @@ +from setuptools import find_packages, setup + +setup(name="kdputils", version="0.1", packages=["kdputils"]) diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..af493ec1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Francesco Cagnin + Copyright 2019 Quarkslab + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LLDBagility/VMSN.py b/LLDBagility/VMSN.py new file mode 100644 index 00000000..2516185e --- /dev/null +++ b/LLDBagility/VMSN.py @@ -0,0 +1,286 @@ +import os +import struct +import mmap + + +class VMSN(str): + def __init__(self, name): + self.mem_fd = open(name+".vmem", "r+") + self.mem_size = os.fstat(self.mem_fd.fileno()).st_size + self.mem_data = mmap.mmap(self.mem_fd.fileno(), self.mem_size) + + self.vmsn_fd = open(name+".vmsn", "r+") + self.vmsn_size = os.fstat(self.vmsn_fd.fileno()).st_size + self.vmsn_data = mmap.mmap(self.vmsn_fd.fileno(), self.vmsn_size) + + + #WARNING !!! TODO !!!!! This is a PoC ! + #This is crappy, I know but ok for now ! + #We need to parse!!! + #XXX THOSE OFFSETS WILL NOT WORK FOR YOUR VMSN !!! + CPU_INFO_INDEX = (0x713C, 43, 1123) #CPU0 in my case + CPU_INFO_INDEX = (0x8CC0, 43, 1100) #CPU1 in my case + + self.vmsn_fd.seek(CPU_INFO_INDEX[0], 0) + Name = self.vmsn_fd.read(3) + Cpuid = struct.unpack("B", self.vmsn_fd.read(1)) + Null = self.vmsn_fd.read(3) + if Name != "rip": + print "Name %s" % list(Name) + print "Failed !" + return False + + self.rip = struct.unpack("Q", self.vmsn_fd.read(8))[0] + print "rip 0x%x" % (self.rip) + + self.vmsn_fd.seek(CPU_INFO_INDEX[0]+CPU_INFO_INDEX[1], 0) + Name = self.vmsn_fd.read(6) + Cpuid = struct.unpack("B", self.vmsn_fd.read(1)) + Null = self.vmsn_fd.read(5) + if Name != "gpregs": + print "Name gpregs %s" % list(Name) + print "Failed !" + return False + + self.rax = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.rbx = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.rcx = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.rdx = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.rdi = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.rsi = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.rsp = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.rbp = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.r8 = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.r9 = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.r10 = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.r11 = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.r12 = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.r13 = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.r14 = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.r15 = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.cs = 0x0 + self.fs = 0x0 + self.gs = 0x0 + + self.vmsn_fd.seek(CPU_INFO_INDEX[0]+CPU_INFO_INDEX[2], 0) + Name = self.vmsn_fd.read(4) + Cpuid = struct.unpack("B", self.vmsn_fd.read(1)) + Null = self.vmsn_fd.read(5) + if Name != "CR64": + print "Name %s" % list(Name) + print "Failed !" + return False + + struct.unpack("Q", self.vmsn_fd.read(8))[0] + struct.unpack("Q", self.vmsn_fd.read(8))[0] + struct.unpack("Q", self.vmsn_fd.read(8))[0] + struct.unpack("Q", self.vmsn_fd.read(8))[0] + struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.cr3 = struct.unpack("Q", self.vmsn_fd.read(8))[0] + self.rflags = 0x0000000000010246 + + def GetCpuCount(self): + return 1 + + def Pause(self): + return True + + def UnsetAllBreakpoint(self): + return True + + def SetBreakpoint(self, a0, a1, a2, a3, a4, a5, a6): + return 0 + + def ReadyPhysicalMemory(self, address, size): + return self.mem_data[address:address+size] + + def ReadPhysical64(self, address): + data = self.mem_data[address:address+8] + return struct.unpack("Q", data)[0] + + def V2P(self, address): + _4K = 4*1024 + _2M = 2*1024*1024 + PML4E_index = (address & 0x0000FF8000000000) >> (9 + 9 + 9 + 12) + PDPE_index = (address & 0x0000007FC0000000) >> (9 + 9 + 12); + PDE_index = (address & 0x000000003FE00000) >> (9 + 12); + PTE_index = (address & 0x00000000001FF000) >> (12); + P_offset = (address & 0x0000000000000FFF); + CR3 = self.cr3 & 0xFFFFFFFFFFFFF000 + + PDPE_base = self.ReadPhysical64(CR3 + (PML4E_index * 8)) & 0x0000FFFFFFFFF000; + if ((PDPE_base == 0) or (PDPE_base > (self.mem_size - _4K))): + return None; + tmp = self.ReadPhysical64(PDPE_base + (PDPE_index * 8)); + if (tmp & 0x80): # This pas is a huge one (1G) ! + print "TODO !! HUGE !!!"; + return None; + PDE_base = tmp & 0x0000FFFFFFFFF000; + if ((PDE_base == 0) or (PDE_base > (self.mem_size - _4K))): + return None; + tmp = self.ReadPhysical64(PDE_base + (PDE_index * 8)); + if ((tmp & 0x1) == 0): + return None; + if (tmp & 0x80): #This page is a large one (2M) ! + tmpPhysical = ((tmp & 0xfffffffe00000) | (address & 0x00000000001FFFFF)); + if ((tmpPhysical == 0) or (tmpPhysical > (self.mem_size - _2M))): + return None; + return (_2M, tmpPhysical) + PTE_base = tmp & 0x0000FFFFFFFFF000; + if ((PTE_base == 0) or (PTE_base > (self.mem_size - _4K))): + return None; + tmp = self.ReadPhysical64(PTE_base + (PTE_index * 8)) + if ((tmp & 0x1) == 0): + return None; + P_base = tmp & 0x0000FFFFFFFFF000; + if ((P_base == 0) or (P_base > (self.mem_size - _4K))): + return None; + return (_4K, (P_base | P_offset)) + + + def ReadVirtualMemory(self, address, size): + data = "" + left_to_read = size + while left_to_read > 0: + ret = self.V2P(address) + if ret == None: + return None + (page_size, physical_address) = ret + page_base = physical_address & ~(page_size - 1) + page_end = page_base + page_size + left_on_page = page_end - physical_address + byte_to_read = min(left_on_page, left_to_read) + tmp_data = self.ReadyPhysicalMemory(physical_address, byte_to_read) + if tmp_data == None: + return None + data += tmp_data + left_to_read = left_to_read - byte_to_read + return data + + def GetStateChanged(self): + return False + + def GetState(self): + return self.STATE_PAUSED + + def SingleStep(self): + return True + + def Resume(self): + return True + + def UnsetBreakpoint(self, a0): + return True + + +#TODO ! Use VMSN parsing !!!!! +""" +#!/usr/bin/python + +import struct +import sys +import mmap + +def nullstrip(s): + try: + return s[:s.index('\x00')] + except: + return s + +def dump(f): + for i in range(0, 5): + for j in range(0, 16): + Unk3 = struct.unpack("B", f.read(1))[0] + print "%02x " % Unk3, + print "" + +f = open(VMSN_FILENAME) + +Magic = struct.unpack("I", f.read(4))[0] +print "Magic %x" % Magic +Unk = struct.unpack("I", f.read(4))[0] +print "Unk %d" % Unk +GroupCount = struct.unpack("I", f.read(4))[0] +print "GroupCount %d" % GroupCount + +for i in range(0, GroupCount): + Name = nullstrip(f.read(64)) + print "Name %s" % Name + TagOffset = struct.unpack("Q", f.read(8))[0] + print "TagOffset %d" % (TagOffset) + Unk2 = struct.unpack("Q", f.read(8))[0] + print "Unk2 %d" % Unk2 + + if "cpu" == Name: + f.seek(TagOffset, 0) + for j in range(0, 4): + print "---------------TAG-----------------" + TagFlags = struct.unpack("B", f.read(1))[0] + print "TagFlags %d" % TagFlags + TagDataSize = TagFlags & 0x3F + print "TagDataSize %d" % TagDataSize + TagNameSize = struct.unpack("B", f.read(1))[0] + print "TagNameSize %d" % TagNameSize + TagName = f.read(TagNameSize) + print "TagName %s" % TagName + TagData = f.read(TagDataSize) + #f.read(4) + break + +f.seek(36, 1) + + +#This is crappy, I know but ok for now ! +f.seek(0x8CC0, 0) +Name = nullstrip(f.read(3)) +Cpuid = struct.unpack("B", f.read(1)) +Null = f.read(3) +if Name != "rip": + print "Name %s" % list(Name) + print "Failed !" + sys.exit(0) +rip = struct.unpack("Q", f.read(8))[0] +print "rip 0x%x" % (rip) + +f.seek(28, 1) +Name = nullstrip(f.read(6)) +Cpuid = struct.unpack("B", f.read(1)) +Null = f.read(5) +if Name != "gpregs": + print "Failed !" + sys.exit(0) + +#6 rsp +#7 rbp +register_names = [ "rax", + "rbx", + "rcx", + "rdx", + "rdi", + "rsi", + "rbp", + "rsp", + "r8", + "r9", + "r10", + "r11", + "r12", + "r13", + "r14", + "r15", + "rip", + "rflags", + "cs", + "fs", + "gs", + ] +for i in range(0, 20): + print "%s 0x%x" % (register_names[i], struct.unpack("Q", f.read(8))[0]) + +f.seek(908, 1) + + + +dump(f) +sys.exit(0) +""" diff --git a/LLDBagility/kdpserver.py b/LLDBagility/kdpserver.py new file mode 100755 index 00000000..79ffda62 --- /dev/null +++ b/LLDBagility/kdpserver.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +import itertools +import socket +import time + +import kdputils.replies +import kdputils.requests +import lldbagilityutils +from kdputils.protocol import ( + KDP_FEATURE_BP, + KDP_VERSION, + MAX_KDP_DATA_SIZE, + KDPError, + KDPRequest, +) + +logger = lldbagilityutils.create_logger(__name__, "/tmp/kdpserver.log") + +# https://github.com/apple/darwin-xnu/blob/xnu-4903.221.2/osfmk/mach/i386/thread_status.h +x86_THREAD_STATE64 = 0x4 +x86_FLOAT_STATE64 = 0x5 + +# https://github.com/apple/darwin-xnu/blob/xnu-4903.221.2/osfmk/mach/i386/_structs.h#L658 +_STRUCT_X86_THREAD_STATE64 = ( + "rax", + "rbx", + "rcx", + "rdx", + "rdi", + "rsi", + "rbp", + "rsp", + "r8", + "r9", + "r10", + "r11", + "r12", + "r13", + "r14", + "r15", + "rip", + "rflags", + "cs", + "fs", + "gs", +) + + +class KDPServer: + def __init__(self): + self.sv_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sv_sock.setblocking(False) + self.sv_sock.bind(("127.0.0.1", 0)) + + self._cl_host = None + self._cl_reply_port = None + self._cl_exception_port = None + self._cl_exception_seq = None + self._cl_session_key = 0x1337 + self._cl_connected = False + + self._continue_debug_loop = True + + def _process(self, vm, reqpkt, cl_addr): + if reqpkt["request"] == KDPRequest.KDP_CONNECT: + assert self._cl_host and self._cl_reply_port + self._cl_exception_port = reqpkt["exc_note_port"] + self._cl_exception_seq = itertools.cycle(range(256)) + self._cl_connected = True + vm.halt() + vm.unset_all_breakpoints() + replypkt = kdputils.replies.kdp_connect(KDPError.KDPERR_NO_ERROR) + + elif reqpkt["request"] == KDPRequest.KDP_DISCONNECT: + assert self._cl_connected + self._cl_connected = False + self._continue_debug_loop = False + vm.unset_all_breakpoints() + replypkt = kdputils.replies.kdp_disconnect() + + elif reqpkt["request"] == KDPRequest.KDP_HOSTINFO: + assert self._cl_connected + cpus_mask, cpu_type, cpu_subtype = vm.get_host_info() + replypkt = kdputils.replies.kdp_hostinfo(cpus_mask, cpu_type, cpu_subtype) + + elif reqpkt["request"] == KDPRequest.KDP_VERSION: + assert self._cl_connected + version, feature = KDP_VERSION, KDP_FEATURE_BP + replypkt = kdputils.replies.kdp_version(version, feature) + + elif reqpkt["request"] == KDPRequest.KDP_READREGS: + assert self._cl_connected + if reqpkt["flavor"] == x86_THREAD_STATE64: + regs = vm.read_registers(_STRUCT_X86_THREAD_STATE64) + replypkt = kdputils.replies.kdp_readregs(KDPError.KDPERR_NO_ERROR, regs) + elif reqpkt["flavor"] == x86_FLOAT_STATE64: + raise NotImplementedError + else: + regs = {} + replypkt = kdputils.replies.kdp_readregs( + KDPError.KDPERR_BADFLAVOR, regs + ) + + elif reqpkt["request"] == KDPRequest.KDP_WRITEREGS: + assert self._cl_connected + if reqpkt["flavor"] == x86_THREAD_STATE64: + regs = { + k: v for k, v in reqpkt.items() if k in _STRUCT_X86_THREAD_STATE64 + } + vm.write_registers(regs) + replypkt = kdputils.replies.kdp_writeregs(KDPError.KDPERR_NO_ERROR) + elif reqpkt["flavor"] == x86_FLOAT_STATE64: + raise NotImplementedError + else: + replypkt = kdputils.replies.kdp_writeregs(KDPError.KDPERR_BADFLAVOR) + + elif reqpkt["request"] == KDPRequest.KDP_RESUMECPUS: + assert self._cl_connected + vm.resume() + replypkt = kdputils.replies.kdp_resumecpus() + + elif reqpkt["request"] == KDPRequest.KDP_REATTACH: + assert not self._cl_connected + self._cl_host, self._cl_reply_port = cl_addr + replypkt = kdputils.replies.kdp_reattach() + + elif reqpkt["request"] == KDPRequest.KDP_READMEM64: + assert self._cl_connected + if reqpkt["nbytes"] > MAX_KDP_DATA_SIZE: + data = b"" + replypkt = kdputils.replies.kdp_readmem64( + KDPError.KDPERR_BAD_NBYTES, data + ) + else: + data = vm.read_virtual_memory(reqpkt["address"], reqpkt["nbytes"]) + if len(data) != reqpkt["nbytes"]: + replypkt = kdputils.replies.kdp_readmem64( + KDPError.KDPERR_BAD_ACCESS, data + ) + else: + replypkt = kdputils.replies.kdp_readmem64( + KDPError.KDPERR_NO_ERROR, data + ) + + elif reqpkt["request"] == KDPRequest.KDP_WRITEMEM64: + assert self._cl_connected + if reqpkt["nbytes"] > MAX_KDP_DATA_SIZE: + replypkt = kdputils.replies.kdp_writemem64(KDPError.KDPERR_BAD_NBYTES) + else: + assert reqpkt["nbytes"] == len(reqpkt["data"]) + vm.write_virtual_memory(reqpkt["address"], reqpkt["data"]) + replypkt = kdputils.replies.kdp_writemem64(KDPError.KDPERR_NO_ERROR) + + elif reqpkt["request"] == KDPRequest.KDP_BREAKPOINT64_SET: + assert self._cl_connected + vm.set_soft_exec_breakpoint(reqpkt["address"]) + replypkt = kdputils.replies.kdp_breakpoint64_set(KDPError.KDPERR_NO_ERROR) + + elif reqpkt["request"] == KDPRequest.KDP_BREAKPOINT64_REMOVE: + assert self._cl_connected + vm.unset_soft_breakpoint(reqpkt["address"]) + replypkt = kdputils.replies.kdp_breakpoint64_remove( + KDPError.KDPERR_NO_ERROR + ) + + elif reqpkt["request"] == KDPRequest.KDP_KERNELVERSION: + assert self._cl_connected + kernel_version = vm.get_kernel_version() + replypkt = kdputils.replies.kdp_kernelversion(kernel_version) + + elif reqpkt["request"] == KDPRequest.KDP_EXCEPTION: + assert self._cl_connected + assert reqpkt["is_reply"] + replypkt = None + + else: + raise NotImplementedError + + return replypkt + + def debug(self, vm): + # it is implicitly assumed the first two KDP requests received are + # KDP_REATTACH and KDP_CONNECT (this is always true when LLDB connects) + while self._continue_debug_loop: + time.sleep(0.003) + + try: + # receive a request + reqpkt, cl_addr = kdputils.protocol.recv(self.sv_sock) + except socket.error: + pass + else: + # process the request + replypkt = self._process(vm, reqpkt, cl_addr) + if replypkt: + # send the response + cl_addr = (self._cl_host, self._cl_reply_port) + kdputils.protocol.send( + self.sv_sock, cl_addr, replypkt, reqpkt["seq"], reqpkt["key"] + ) + + if self._cl_connected and vm.is_state_changed(): + _, exception = vm.state() + if exception: + (exception, code, subcode) = exception + reqpkt = kdputils.requests.kdp_exception( + n_exc_info=0x1, + cpu=0x0, + exception=exception, + code=code, + subcode=subcode, + ) + cl_addr = (self._cl_host, self._cl_exception_port) + kdputils.protocol.send( + self.sv_sock, + cl_addr, + reqpkt, + next(self._cl_exception_seq), + self._cl_session_key, + ) diff --git a/LLDBagility/lldbagility.py b/LLDBagility/lldbagility.py new file mode 100755 index 00000000..23a9979a --- /dev/null +++ b/LLDBagility/lldbagility.py @@ -0,0 +1,419 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +from __future__ import print_function + +import argparse +import functools +import re +import shlex +import threading +import time +import traceback + +import kdpserver +import lldb +import lldbagilityutils +import stubvm + +vm = None + + +def _exec_cmd(debugger, command, capture_output=False): + if capture_output: + cmdretobj = lldb.SBCommandReturnObject() + debugger.GetCommandInterpreter().HandleCommand(command, cmdretobj) + return cmdretobj + else: + debugger.HandleCommand(command) + return None + + +def _evaluate_expression(exe_ctx, expression): + res = exe_ctx.frame.EvaluateExpression(expression) + try: + vaddr = int(res.GetValue(), 0) + except (TypeError, ValueError): + return None + else: + return vaddr + + +def fdp_attach(debugger, command, exe_ctx, result, internal_dict): + """ + Connect to a macOS VM via FDP. + The VM must have already been started. + Existing breakpoints are deleted on attaching. + Re-execute this command every time the VM is rebooted. + """ + parser = argparse.ArgumentParser(prog="fdp-attach") + parser.add_argument("vm_name") + args = parser.parse_args(shlex.split(command)) + + _attach(debugger, exe_ctx, stubvm.FDPSTUB, args.vm_name) + + +def vmsn_attach(debugger, command, exe_ctx, result, internal_dict): + """ + Connect to a macOS VM via VMSN. Currently not maintained! + Existing breakpoints are deleted on attaching. + """ + parser = argparse.ArgumentParser(prog="vmsn-attach") + parser.add_argument("vm_name") + args = parser.parse_args(shlex.split(command)) + + _attach(debugger, exe_ctx, stubvm.VMSNSTUB, args.vm_name) + + +def _attach(debugger, exe_ctx, vm_stub, vm_name): + global vm + print(lldbagilityutils.LLDBAGILITY) + + print("* Attaching to the VM") + try: + vm = stubvm.STUBVM(vm_stub, vm_name) + except Exception as exc: + print("* Could not attach! {}".format(str(exc))) + return + + print("* Resuming the VM execution until reaching kernel code") + vm.complete_attach() + print("* Kernel load address: 0x{:016x}".format(vm.kernel_load_vaddr)) + print("* Kernel slide: 0x{:x}".format(vm.kernel_slide)) + print("* Kernel cr3: 0x{:x}".format(vm.kernel_cr3)) + print("* Kernel version: {}".format(vm.kernel_version)) + print("* VM breakpoints deleted") + + # detach the previous process (if any) + exe_ctx.process.Detach() + + # remove all LLDB breakpoints + exe_ctx.target.DeleteAllBreakpoints() + print("* LLDB breakpoints deleted") + + # start the fake KDP server + kdpsv = kdpserver.KDPServer() + th = threading.Thread(target=kdpsv.debug, args=(vm,)) + th.daemon = True + th.start() + + # connect LLDB to the fake KDP server + kdpsv_addr, kdpsv_port = kdpsv.sv_sock.getsockname() + _exec_cmd(debugger, "kdp-remote '{}:{}'".format(kdpsv_addr, kdpsv_port)) + + # trigger a memory write to find out the address of the kdp struct + vm.store_kdp_at_next_write_virtual_memory() + if _exec_cmd(debugger, "memory write &kdp 41", capture_output=True).GetError(): + print("* Unable to find the 'kdp' symbol. Did you specify the target to debug?") + vm.abort_store_kdp_at_next_write_virtual_memory() + + +def _attached(f): + @functools.wraps(f) + def _wrapper(*args, **kwargs): + global vm + if not vm: + print("* Not attached to a VM!") + return + return f(*args, **kwargs) + + return _wrapper + + +@_attached +def fdp_save(debugger, command, exe_ctx, result, internal_dict): + """ + Save the current state of the attached macOS VM. + Breakpoints are not saved (but retained for the current session). + """ + # saving the state causes all breakpoints (soft and hard) to be unset, but + # we can preserve them at least for the current session + + # we disable soft breakpoints before saving and then re-enable them once the state + # has been saved, so that LLDB sends again the KDP requests for setting them + exe_ctx.target.DisableAllBreakpoints() + # similarly, for hard breakpoints we save the state of the debug registers before saving, + # and restore it afterwards + dbgregs = vm.read_registers(("dr0", "dr1", "dr2", "dr3", "dr6", "dr7")) + + # interrupt and save the VM state + process_was_stopped = exe_ctx.process.is_stopped + print("* Saving the VM state") + vm.interrupt_and_take_snapshot() + print("* State saved") + + # restore soft breakpoints + exe_ctx.target.EnableAllBreakpoints() + # restore hard breakpoints + vm.write_registers(dbgregs) + + if not process_was_stopped: + # display stop info + _exec_cmd(debugger, "process status") + + +@_attached +def fdp_restore(debugger, command, exe_ctx, result, internal_dict): + """ + Restore the attached macOS VM to the last saved state. + Breakpoints are deleted on restoring. + """ + # interrupt and restore the VM state + print("* Restoring the last saved VM state") + if vm.interrupt_and_restore_last_snapshot(): + print("* State restored") + # do a full reattach (the kernel load address may differ) + fdp_attach(debugger, vm.name, exe_ctx, result, internal_dict) + else: + print("* No saved state found") + + +@_attached +def fdp_interrupt(debugger, command, exe_ctx, result, internal_dict): + """ + Interrupt (pause) the execution of the attached macOS VM. + """ + vm.interrupt() + + +@_attached +def fdp_hbreakpoint(debugger, command, exe_ctx, result, internal_dict): + """ + Set or unset hardware breakpoints. + Hardware breakpoints are implemented using the debug registers DR0, DR1, DR2 and DR3. + Consequently, a maximum of four hardware breakpoints can be active simultaneously. + """ + parser = argparse.ArgumentParser(prog="fdp-hbreakpoint") + subparsers = parser.add_subparsers(dest="action") + + set_parser = subparsers.add_parser("set") + set_parser.add_argument( + "trigger", + choices={"e", "rw", "w"}, + help="Type of memory access to trap on: execute, read/write, or write only.", + ) + set_parser.add_argument( + "nreg", + type=lambda i: int(i, 0), + choices={0, 1, 2, 3}, + help="Breakpoint slot to use (corresponding to registers ).", + ) + set_parser.add_argument( + "expression", help="Breakpoint address or expression to be evaluated as such." + ) + + unset_parser = subparsers.add_parser("unset") + unset_parser.add_argument( + "nreg", + type=lambda i: int(i, 0), + choices={0, 1, 2, 3}, + help="Breakpoint slot to free (corresponding to registers DR0, DR1, DR2 and DR3).", + ) + + args = parser.parse_args(shlex.split(command)) + if args.action == "set": + vaddr = _evaluate_expression(exe_ctx, args.expression) + if vaddr: + vm.set_hard_breakpoint(args.trigger, args.nreg, vaddr) + print("* Hardware breakpoint set: address = 0x{:016x}".format(vaddr)) + else: + print("* Invalid expression") + elif args.action == "unset": + vm.unset_hard_breakpoint(args.nreg) + print("* Hardware breakpoint unset") + else: + raise AssertionError + + +@_attached +def fdp_test(debugger, command, exe_ctx, result, internal_dict): + """ + Run some tests. + Warning: tests change the state of the machine and modify the last saved state! + """ + regs = { + "rax", + "rbx", + "rcx", + "rdx", + "rdi", + "rsi", + "rbp", + "rsp", + "r8", + "r9", + "r10", + "r11", + "r12", + "r13", + "r14", + "r15", + "rip", + "rflags", + "cs", + "fs", + "gs", + } + + def _t1(): + print("* Halt/resume/single step") + vm.halt() + assert vm.is_state_halted() + + vm.resume() + assert not vm.is_state_halted() + + vm.halt() + for _ in range(100): + vm.single_step() + assert vm.is_state_halted() + + def _t2(): + print("* Read/write registers") + vm.halt() + + orig_values = vm.read_registers(regs) + + new_values = {reg: 0x1337 for reg in regs} + for reg in regs: + vm.write_register(reg, new_values[reg]) + # modifications to RFLAGS should be disabled + assert vm.read_register("rflags") == orig_values["rflags"] + del new_values["rflags"] + assert vm.read_registers(regs - {"rflags"}) == new_values + + vm.write_registers(orig_values) + for reg in regs: + assert vm.read_register(reg) == orig_values[reg] + + def _t3(): + print("* Read/write virtual memory") + vm.halt() + + data = vm.read_virtual_memory(vm.read_register("rsp"), 0x8) + + new_data = b"ABCDEFGH" + vm.write_virtual_memory(vm.read_register("rsp"), new_data) + assert vm.read_virtual_memory(vm.read_register("rsp"), 0x8) == new_data + + vm.write_virtual_memory(vm.read_register("rsp"), data) + assert vm.read_virtual_memory(vm.read_register("rsp"), 0x8) == data + + def _t4(): + print("* Save/restore") + vm.halt() + + orig_values = vm.read_registers(regs) + orig_data = vm.read_virtual_memory(vm.read_register("rsp"), 0x100) + vm.interrupt_and_take_snapshot() + assert vm.is_state_halted() + + vm.write_virtual_memory(vm.read_register("rsp"), b"A" * 0x100) + vm.single_step() + vm.resume() + time.sleep(0.100) + + vm.interrupt_and_restore_last_snapshot() + assert vm.is_state_halted() + assert not vm.is_breakpoint_hit() + assert vm.read_registers(regs) == orig_values + assert vm.read_virtual_memory(vm.read_register("rsp"), 0x100) == orig_data + + def _t5(): + print("* Debug registers") + vm.halt() + vm.write_register("dr7", 0x0) + + vm.set_hard_breakpoint("rw", 0x0, 0x1234) + assert vm.read_register("dr0") == 0x1234 + assert vm.read_register("dr7") == 0b00000000000000110000000000000010 + vm.set_hard_breakpoint("e", 0x0, 0x1234) + assert vm.read_register("dr7") == 0b00000000000000000000000000000010 + vm.set_hard_breakpoint("w", 0x0, 0x1234) + assert vm.read_register("dr7") == 0b00000000000000010000000000000010 + + vm.set_hard_breakpoint("rw", 0x1, 0x1234) + assert vm.read_register("dr1") == 0x1234 + assert vm.read_register("dr7") == 0b00000000001100010000000000001010 + vm.set_hard_breakpoint("rw", 0x2, 0x1234) + assert vm.read_register("dr2") == 0x1234 + assert vm.read_register("dr7") == 0b00000011001100010000000000101010 + vm.set_hard_breakpoint("rw", 0x3, 0x1234) + assert vm.read_register("dr3") == 0x1234 + assert vm.read_register("dr7") == 0b00110011001100010000000010101010 + + vm.unset_hard_breakpoint(0x0) + assert vm.read_register("dr7") == 0b00110011001100010000000010101000 + vm.unset_hard_breakpoint(0x1) + assert vm.read_register("dr7") == 0b00110011001100010000000010100000 + vm.unset_hard_breakpoint(0x2) + assert vm.read_register("dr7") == 0b00110011001100010000000010000000 + vm.unset_hard_breakpoint(0x3) + assert vm.read_register("dr7") == 0b00110011001100010000000000000000 + + def _t6(): + print("* Soft/hard exec breakpoint") + vm.halt() + + # keep in mind that FDP soft and page breakpoints do not work just after a restore + # (see VMR3AddSoftBreakpoint()) + + vm.unset_all_breakpoints() + vm.single_step() + assert not vm.is_breakpoint_hit() + + vm.interrupt_and_take_snapshot() + vm.single_step() + vm.single_step() + rip = vm.read_register("rip") + + vm.interrupt_and_restore_last_snapshot() + vm.single_step() + bpid = vm.set_soft_exec_breakpoint(rip) + assert 0 <= bpid <= 254 + assert not vm.is_breakpoint_hit() + vm.resume() + time.sleep(0.100) + vm.halt() + assert vm.is_breakpoint_hit() + + vm.interrupt_and_restore_last_snapshot() + vm.single_step() + vm.set_hard_breakpoint("e", 0x0, rip) + assert not vm.is_breakpoint_hit() + vm.resume() + time.sleep(0.100) + vm.halt() + assert vm.is_breakpoint_hit() + + if exe_ctx.process.is_running: + vm.interrupt() + vm.unset_all_breakpoints() + + for _t in (_t1, _t2, _t3, _t4, _t5, _t6): + _t() + print("* All tests passed!") + + +def __lldb_init_module(debugger, internal_dict): + # FDP + debugger.HandleCommand("command script add -f lldbagility.fdp_attach fdp-attach") + debugger.HandleCommand("command script add -f lldbagility.fdp_save fdp-save") + debugger.HandleCommand("command script add -f lldbagility.fdp_restore fdp-restore") + debugger.HandleCommand( + "command script add -f lldbagility.fdp_interrupt fdp-interrupt" + ) + debugger.HandleCommand( + "command script add -f lldbagility.fdp_hbreakpoint fdp-hbreakpoint" + ) + debugger.HandleCommand("command script add -f lldbagility.fdp_test fdp-test") + + debugger.HandleCommand("command alias fa fdp-attach") + debugger.HandleCommand("command alias fs fdp-save") + debugger.HandleCommand("command alias fr fdp-restore") + debugger.HandleCommand("command alias fi fdp-interrupt") + debugger.HandleCommand("command alias fh fdp-hbreakpoint") + + # VMSN + debugger.HandleCommand("command script add -f lldbagility.vmsn_attach vmsn-attach") + + debugger.HandleCommand("command alias va vmsn-attach") diff --git a/LLDBagility/lldbagilityutils.py b/LLDBagility/lldbagilityutils.py new file mode 100755 index 00000000..8b665dfe --- /dev/null +++ b/LLDBagility/lldbagilityutils.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +import functools +import logging +import os +import struct + +BOLD = "\033[1m" +RED = "\033[91m" +CLEAR = "\033[0m" +LLDBAGILITY = BOLD + RED + "LLDBagility" + CLEAR + + +def p32(i): + return struct.pack(" stopping: 0x{:016x}".format(self.read_register("rip"))) + break + self.stub.SingleStep() + self.stub.UnsetBreakpoint(cr3bp_id) + + @lldbagilityutils.indented(logger) + def _get_active_thread_vaddr(self): + logger.debug("_get_active_thread_vaddr()") + # https://github.com/apple/darwin-xnu/blob/xnu-4903.221.2/osfmk/i386/cpu_data.h#L392 + + def _get_gs_base(self): + logger.debug("_get_gs_base()") + + gs_base = self.read_msr64(MSR_IA32_GS_BASE) + logger.debug("> MSR_IA32_GS_BASE: 0x{:016x}".format(gs_base)) + if not _in_kernel_space(gs_base): + gs_base = self.read_msr64(MSR_IA32_KERNEL_GS_BASE) + logger.debug("> MSR_IA32_KERNEL_GS_BASE: 0x{:016x}".format(gs_base)) + return gs_base + + # https://github.com/apple/darwin-xnu/blob/xnu-4903.221.2/osfmk/i386/mp_desc.c#L476 + cpu_data_vaddr = _get_gs_base(self) + logger.debug("> cpu_data_vaddr: 0x{:016x}".format(cpu_data_vaddr)) + + # https://github.com/apple/darwin-xnu/blob/xnu-4903.221.2/osfmk/i386/cpu_data.h#L149 + cpu_this = lldbagilityutils.u64(self.read_virtual_memory(cpu_data_vaddr, 0x8)) + logger.debug("> cpu_this: 0x{:016x}".format(cpu_this)) + assert cpu_data_vaddr == cpu_this + + # https://github.com/apple/darwin-xnu/blob/xnu-4903.221.2/osfmk/i386/cpu_data.h#L150 + cpu_active_thread = lldbagilityutils.u64( + self.read_virtual_memory(cpu_data_vaddr + 0x8, 0x8) + ) + logger.debug("> cpu_active_thread: 0x{:016x}".format(cpu_active_thread)) + return cpu_active_thread + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def complete_attach(self): + logger.debug("complete_attach()") + self.halt() + self.unset_all_breakpoints() + self._continue_until_kernel_code() + + assert _in_kernel_space(self.read_register("rip")) + self.kernel_cr3 = self.read_register("cr3") + logger.debug("> kernel_cr3: 0x{:x}".format(self.kernel_cr3)) + + self.kernel_load_vaddr = _find_kernel_load_vaddr(self) + logger.debug("> kernel_load_vaddr: 0x{:016x}".format(self.kernel_load_vaddr)) + self.kernel_slide = _compute_kernel_slide(self.kernel_load_vaddr) + logger.debug("> kernel_slide: 0x{:x}".format(self.kernel_slide)) + self.kernel_version = _find_kernel_version(self) + logger.debug("> kernel_version: {}".format(self.kernel_version)) + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def get_num_cpus(self): + logger.debug("get_num_cpus()") + return self.stub.GetCpuCount() + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def get_host_info(self): + logger.debug("get_host_info()") + # https://github.com/apple/darwin-xnu/blob/xnu-4903.221.2/osfmk/kdp/ml/x86_64/kdp_machdep.c#L256 + cpus_mask = 0x0 + for i in range(self.get_num_cpus()): + cpus_mask |= 1 << i + cpu_type = CPU_TYPE_X86_64 + cpu_subtype = CPU_SUBTYPE_X86_ARCH1 + return cpus_mask, cpu_type, cpu_subtype + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def get_kernel_version(self): + logger.debug("get_kernel_version()") + kernel_version = self.kernel_version + if b"stext" not in kernel_version: + logger.debug("> stext") + # return the known kernel load address to make LLDB do less requests + kernel_version += "; stext=0x{:016x}".format(self.kernel_load_vaddr) + return kernel_version + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def read_msr64(self, msr): + logger.debug("read_msr64(msr=0x{:x})".format(msr)) + return self.stub.ReadMsr(msr, CpuId=self.stub.CPU0) + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def write_msr64(self, msr, val): + logger.debug("write_msr64(msr=0x{:x}, val=0x{:x})".format(msr, val)) + self.stub.WriteMsr(self, msr, val, CpuId=self.stub.CPU0) + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def read_register(self, reg): + logger.debug("read_register(reg='{}')".format(reg)) + val = getattr(self.stub, reg) + if reg == "rip" and self._return_incremented_at_next_read_register_rip: + logger.debug("> _return_incremented_at_next_read_register_rip") + self._return_incremented_at_next_read_register_rip = False + # https://github.com/llvm/llvm-project/tree/llvmorg-8.0.0/lldb/source/Plugins/Process/MacOSX-Kernel/ThreadKDP.cpp#L157 + # https://github.com/llvm/llvm-project/tree/llvmorg-8.0.0/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp#L571 + return val + 1 + return val + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def read_registers(self, regs): + logger.debug("read_registers()") + return {reg: self.read_register(reg) for reg in regs} + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def write_register(self, reg, val): + logger.debug("write_register(reg='{}', val=0x{:x})".format(reg, val)) + if reg == "rflags": + if val & EFL_TF: + logger.debug("> _singlestep_at_next_resume") + self._singlestep_at_next_resume = True + # disallow changes to RFLAGS + return + setattr(self.stub, reg, val) + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def write_registers(self, regs): + logger.debug("write_registers()") + for reg, val in regs.items(): + self.write_register(reg, val) + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def read_virtual_memory(self, vaddr, nbytes): + logger.debug( + "read_virtual_memory(vaddr=0x{:016x}, nbytes=0x{:x})".format(vaddr, nbytes) + ) + data = self.stub.ReadVirtualMemory(vaddr, nbytes) + + if not data and not _in_kernel_space(self.read_register("rip")): + # if reading fails, it could be the case that we are trying to read kernel + # virtual addresses from user space (e.g. when LLDB stops in user land and + # the user loads or uses lldbmacros) + # in this case, we try the read again but using the kernel pmap + logger.debug("> using kernel pmap") + process_cr3 = self.read_register("cr3") + # switch to kernel pmap + self.write_register("cr3", self.kernel_cr3) + # try the read again + data = self.stub.ReadVirtualMemory(vaddr, nbytes) + # switch back to the process pmap + self.write_register("cr3", process_cr3) + + if self._kdp_vaddr and vaddr <= self._kdp_vaddr <= vaddr + nbytes: + # this request has very likely been generated by LLDBmacros + logger.debug("> fake kdp struct") + assert data is not None + # fill some fields of the empty (since the boot-arg "debug" is probably not set) kdp struct + saved_state = lldbagilityutils.p64(NULL) + kdp_thread = lldbagilityutils.p64(self._get_active_thread_vaddr()) + fake_partial_kdp_struct = b"".join((saved_state, kdp_thread)) + kdp_struct_offset = self._kdp_vaddr - vaddr + data = ( + data[:kdp_struct_offset] + + fake_partial_kdp_struct + + data[kdp_struct_offset + len(fake_partial_kdp_struct) :] + ) + + data = data if data else b"" + logger.debug("> len(data): 0x{:x}".format(len(data))) + return data + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def write_virtual_memory(self, vaddr, data): + logger.debug("write_virtual_memory(vaddr=0x{:016x}, data=...)".format(vaddr)) + assert self.is_state_halted() + if self._store_kdp_at_next_write_virtual_memory: + logger.debug("> _store_kdp_at_next_write_virtual_memory") + self._store_kdp_at_next_write_virtual_memory = False + self._kdp_vaddr = vaddr + return + return self.stub.WriteVirtualMemory(vaddr, data) + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def set_soft_exec_breakpoint(self, vaddr): + logger.debug("set_soft_exec_breakpoint(vaddr=0x{:016x})".format(vaddr)) + assert self.is_state_halted() + id = 0x0 + length = 0x1 + self._soft_breakpoints[vaddr] = self.stub.SetBreakpoint( + self.stub.SOFT_HBP, + id, + self.stub.EXECUTE_BP, + self.stub.VIRTUAL_ADDRESS, + vaddr, + length, + self.stub.NO_CR3, + ) + logger.debug("> bp id: {}".format(self._soft_breakpoints[vaddr])) + return self._soft_breakpoints[vaddr] + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def unset_soft_breakpoint(self, vaddr): + logger.debug("unset_soft_breakpoint(vaddr=0x{:016x})") + assert self.is_state_halted() + try: + id = self._soft_breakpoints[vaddr] + except KeyError: + logger.debug("> no such breakpoint") + else: + del self._soft_breakpoints[vaddr] + return self.stub.UnsetBreakpoint(id) + return False + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def set_hard_breakpoint(self, trigger, nreg, vaddr): + logger.debug( + "set_hard_exec_breakpoint(trigger='{}', nreg=0x{:016x}, vaddr=0x{:016x})".format( + trigger, nreg, vaddr + ) + ) + assert self.is_state_halted() + assert trigger in ("e", "w", "rw") + assert 0 <= nreg <= 3 + trigger_bitshifts = {nreg: 16 + nreg * 4 for nreg in range(4)} + status_bitshifts = {nreg: nreg * 2 for nreg in range(4)} + + ctrl_mask = self.read_register("dr7") + # reset trigger entry for the chosen register to 0b00 + ctrl_mask &= ~(0b11 << trigger_bitshifts[nreg]) + # set new entry + if trigger == "e": + trigger_entry = 0b00 + elif trigger == "w": + trigger_entry = 0b01 + elif trigger == "rw": + trigger_entry = 0b11 + else: + raise NotImplementedError + ctrl_mask |= trigger_entry << trigger_bitshifts[nreg] + # enable breakpoint globally + ctrl_mask |= 0b10 << status_bitshifts[nreg] + logger.debug("> ctrl_mask: 0b{:032b}".format(ctrl_mask)) + + self.write_register("dr{}".format(nreg), vaddr) + self.write_register("dr7", ctrl_mask) + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def unset_hard_breakpoint(self, nreg): + logger.debug("unset_hard_breakpoint(nreg=0x{:016x})".format(nreg)) + assert self.is_state_halted() + assert 0 <= nreg <= 3 + status_bitshifts = {nreg: nreg * 2 for nreg in range(4)} + + ctrl_mask = self.read_register("dr7") + # disable breakpoint globally and locally + ctrl_mask &= ~(0b11 << status_bitshifts[nreg]) + logger.debug("> ctrl_mask: 0b{:032b}".format(ctrl_mask)) + + self.write_register("dr{}".format(nreg), 0x0) + self.write_register("dr7", ctrl_mask) + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def unset_all_breakpoints(self): + logger.debug("unset_all_breakpoints()") + assert self.is_state_halted() + # remove soft breakpoints + self._soft_breakpoints.clear() + self.stub.UnsetAllBreakpoint() + # remove hard breakpoints + self.write_register("dr0", 0x0) + self.write_register("dr1", 0x0) + self.write_register("dr2", 0x0) + self.write_register("dr3", 0x0) + self.write_register("dr6", 0x0) + self.write_register("dr7", 0x0) + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def halt(self): + logger.debug("halt()") + self.stub.Pause() + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def interrupt(self): + logger.debug("interrupt()") + self._exception = (EXC_SOFTWARE, EXC_SOFT_SIGNAL, SIGINT) + self.halt() + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def single_step(self): + logger.debug("single_step()") + self._exception = (EXC_BREAKPOINT, EXC_I386_BPTFLT, 0x0) + self.stub.SingleStep() + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def resume(self): + logger.debug("resume()") + + if self._interrupt_at_next_resume: + logger.debug("> _interrupt_at_next_resume") + self._interrupt_at_next_resume = False + self.interrupt() + return + + if self._singlestep_at_next_resume: + logger.debug("> _singlestep_at_next_resume") + self._singlestep_at_next_resume = False + self.single_step() + return + + if self.is_breakpoint_hit(): + logger.debug( + "> state breakpoint hit: 0x{:016x}".format(self.read_register("rip")) + ) + self.stub.SingleStep() + + self.stub.Resume() + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def interrupt_and_take_snapshot(self): + logger.debug("interrupt_and_take_snapshot()") + self.interrupt() + self.stub.Save() + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def interrupt_and_restore_last_snapshot(self): + logger.debug("interrupt_and_restore_last_snapshot()") + self.interrupt() + if self.stub.Restore(): + # breakpoints are not restored + self._soft_breakpoints.clear() + return True + else: + logger.debug("> could not restore") + return False + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def state(self): + logger.debug("state()") + if self.is_breakpoint_hit(): + logger.debug("> state breakpoint hit") + self._exception = (EXC_BREAKPOINT, EXC_I386_BPTFLT, 0x0) + # the following assumes that the next call to STUBVM.read_register("rip") + # will be made by LLDB in response to this EXC_BREAKPOINT exception + self._return_incremented_at_next_read_register_rip = True + state = (self.stub.GetState(), self._exception) + self._exception = None + return state + + @lldbagilityutils.synchronized + def is_state_changed(self): + return self.stub.GetStateChanged() or self._exception + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def is_state_halted(self): + logger.debug("is_state_halted()") + return self.stub.GetState() & self.stub.STATE_PAUSED + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def is_breakpoint_hit(self): + logger.debug("is_breakpoint_hit()") + return self.stub.GetState() & ( + self.stub.STATE_BREAKPOINT_HIT | self.stub.STATE_HARD_BREAKPOINT_HIT + ) + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def interrupt_at_next_resume(self): + logger.debug("interrupt_at_next_resume()") + self._interrupt_at_next_resume = True + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def store_kdp_at_next_write_virtual_memory(self): + logger.debug("store_kdp_at_next_write_virtual_memory()") + self._store_kdp_at_next_write_virtual_memory = True + + @lldbagilityutils.indented(logger) + @lldbagilityutils.synchronized + def abort_store_kdp_at_next_write_virtual_memory(self): + logger.debug("abort_store_kdp_at_next_write_virtual_memory()") + assert not self._kdp_vaddr + self._store_kdp_at_next_write_virtual_memory = False + + +def _in_kernel_space(addr): + return VM_MIN_KERNEL_ADDRESS <= addr <= VM_MAX_KERNEL_ADDRESS + + +@lldbagilityutils.indented(logger) +def _find_kernel_load_vaddr(vm): + logger.debug("_find_kernel_load_vaddr()") + assert _in_kernel_space(vm.read_register("rip")) + + @lldbagilityutils.indented(logger) + def _is_kernel_load_vaddr(vaddr): + logger.debug("_is_kernel_load_vaddr()") + if not _in_kernel_space(vaddr): + return False + data = vm.read_virtual_memory(vaddr, 0x4) + return data and lldbagilityutils.u32(data) == MH_MAGIC_64 + + @lldbagilityutils.indented(logger) + def _get_debug_kernel_load_vaddr(): + logger.debug("_get_debug_kernel_load_vaddr()") + # from the LLDB documentation: "If the debug flag is included in the + # boot-args nvram setting, the kernel's load address will be noted + # in the lowglo page at a fixed address" + # https://github.com/llvm/llvm-project/blob/llvmorg-8.0.0/lldb/source/Plugins/DynamicLoader/Darwin-Kernel/DynamicLoaderDarwinKernel.cpp#L226 + # https://github.com/apple/darwin-xnu/blob/xnu-4903.221.2/osfmk/x86_64/lowglobals.h#L54 + # https://github.com/apple/darwin-xnu/blob/xnu-4903.221.2/osfmk/x86_64/pmap.c#L1175 + lgStext_vaddr = 0xFFFFFF8000002010 + data = vm.read_virtual_memory(lgStext_vaddr, 0x8) + if data: + vaddr = lldbagilityutils.u64(data) + if _is_kernel_load_vaddr(vaddr): + return vaddr + else: + # probably trying to attach to the target before lgStext is initialised + return None + else: + return None + + @lldbagilityutils.indented(logger) + def _search_kernel_load_vaddr(start_vaddr): + logger.debug( + "_search_kernel_load_vaddr(start_vaddr=0x{:016x})".format(start_vaddr) + ) + # try to find the load address manually + assert _in_kernel_space(start_vaddr) + vaddr = start_vaddr & ~(I386_PGBYTES - 1) + while vaddr >= VM_MIN_KERNEL_ADDRESS: + if _is_kernel_load_vaddr(vaddr): + return vaddr + vaddr -= I386_PGBYTES + else: + raise AssertionError + + kernel_load_vaddr = _get_debug_kernel_load_vaddr() or _search_kernel_load_vaddr( + vm.read_register("rip") + ) + return kernel_load_vaddr + + +def _compute_kernel_slide(kernel_load_vaddr): + return kernel_load_vaddr - 0xFFFFFF8000200000 + + +@lldbagilityutils.indented(logger) +def _find_kernel_version(vm): + logger.debug("_find_kernel_version()") + + kernel_macho = b"" + while len(kernel_macho) < 42 * 1024 * 1024: # a reasonable upper bound? + buf = b"" + while len(buf) < 2 * 1024 * 1024: + vaddr = vm.kernel_load_vaddr + len(kernel_macho) + len(buf) + buf += vm.read_virtual_memory(vaddr, I386_PGBYTES) + kernel_macho += buf + try: + kernel_version = re.search( + b"(?PDarwin Kernel Version .+?X86_64)\0", kernel_macho + ).group("version") + except AttributeError: + continue + else: + return kernel_version + else: + raise AssertionError + + +class FDPSTUB(FDP): + NO_CR3 = FDP.FDP_NO_CR3 + + SOFT_HBP = FDP.FDP_SOFTHBP + CR_HBP = FDP.FDP_CRHBP + + VIRTUAL_ADDRESS = FDP.FDP_VIRTUAL_ADDRESS + + EXECUTE_BP = FDP.FDP_EXECUTE_BP + WRITE_BP = FDP.FDP_WRITE_BP + + STATE_PAUSED = FDP.FDP_STATE_PAUSED + STATE_BREAKPOINT_HIT = FDP.FDP_STATE_BREAKPOINT_HIT + STATE_HARD_BREAKPOINT_HIT = FDP.FDP_STATE_HARD_BREAKPOINT_HIT + + CPU0 = FDP.FDP_CPU0 + + def __init__(self, name): + super(FDPSTUB, self).__init__(name) + assert self.GetCpuCount() == 1, ( + "VMs with more than one CPU are not fully supported by FDP! " + "Decrease the number of processors in the VM settings" + ) + + +class VMSNSTUB(VMSN): + NO_CR3 = 0 + + SOFT_HBP = 2 + CR_HBP = 0 + + VIRTUAL_ADDRESS = 0 + + EXECUTE_BP = 0 + WRITE_BP = 0 + + STATE_PAUSED = 1 + STATE_BREAKPOINT_HIT = 1 + STATE_HARD_BREAKPOINT_HIT = 0 + + CPU0 = FDP.FDP_CPU0 + + def __init__(self, name): + super(VMSNSTUB, self).__init__(name) diff --git a/README.md b/README.md new file mode 100644 index 00000000..7f7ded19 --- /dev/null +++ b/README.md @@ -0,0 +1,160 @@ +# LLDBagility +LLDBagility is a tool for **debugging macOS virtual machines** with the aid of the Fast Debugging Protocol (FDP). + +For all information, read the accompanying blog posts: + +- [An overview of macOS kernel debugging](https://blog.quarkslab.com/an-overview-of-macos-kernel-debugging.html) +- [LLDBagility: practical macOS kernel debugging](https://blog.quarkslab.com/lldbagility-practical-macos-kernel-debugging.html) + +## Features +LLDBagility implements a set of new LLDB commands that allows the debugger to: +- attach to running macOS VirtualBox virtual machines and debug their kernel, stealthily, without the need of changing the guest OS (e.g. no necessity of DEVELOPMENT or DEBUG kernels, boot-args modification or SIP disabling) and with minimal changes to the configuration of the VM; +- interrupt (and later resume) the execution of the guest kernel at any moment; +- set hardware breakpoints anywhere in kernel code, even at the start of the boot process; +- set hardware watchpoints that trigger on read and/or write accesses of the specified memory locations; +- save and restore the state of the VM in a few seconds. + +## Files +- [DWARFutils/](DWARFutils/): scripts for working with DWARF files +- [FDPutils/](FDPutils/): Fast Debugging Protocol for macOS hosts and VirtualBox 5.2.14 and 6.0.8 +- [KDKutils/](KDKutils/): scripts for working with Kernel Debug Kits (KDKs) and lldbmacros +- [KDPutils/](KDPutils/): Python reimplementation of the KDP protocol +- [LLDBagility/](LLDBagility/): the tool +- [misc/](misc/): helper scripts for creating macOS Mojave VMs + +In the Releases section: +- `data.zip`: kernels and lldbmacros used in some of the examples +- `VirtualBox-5.2.14_FDP.app.zip`: prebuilt VirtualBox 5.2.14 app with the FDP patch for macOS hosts +- `VirtualBox-6.0.8_FDP.app.zip`: prebuilt VirtualBox 6.0.8 app with the FDP patch for macOS hosts + +## Requisites +- A **recent version of macOS as host OS**, with the LLDB debugger (can be installed with e.g. `xcode-select --install`) +- A working build of VirtualBox with the FDP patch for macOS hosts along with the PyFDP bindings (instructions in the dedicated [README](FDPutils/)) +- A VirtualBox VM with any version of macOS as guest OS +- A copy of the macOS kernel binary of the guest (not needed if the guest has the same kernel of the host, or if the Kernel Debug Kit of the guest kernel is installed in the host) +- The KDPutils Python package (instructions in the dedicated [README](KDPutils/)) + +Note that both packages PyFDP and KDPutils must be installed for the Python version used by LLDB (likely Python 2). LLDBagility has been tested with LLDB from the Command Line Tools and the Python 2 interpreter shipped with macOS, but other versions of these software should work as well; for example, to use Python 2 from Homebrew run LLDB with `env DYLD_FRAMEWORK_PATH="$(brew --prefix python@2)/Frameworks/" lldb`. + +## Installation +Assuming all requisites are satisfied, simply add `command script import /LLDBagility/lldbagility.py` to `~/.lldbinit`. + +## Usage +1. Start the macOS virtual machine to debug and LLDB; +2. (required only if the kernel binary of the guest is different from the kernel of the host and no KDK for the guest kernel is installed in the host) in LLDB, execute the command `target create `; +3. in LLDB, execute the command `fdp-attach ` to start debugging the VM. + +The new LLDB commands implemented by LLDBagility are: +- `fdp-attach` or `fa`, to connect the debugger to a running macOS VirtualBox virtual machine; +- `fdp-hbreakpoint` or `fh`, to set and unset read/write/execute hardware breakpoints; +- `fdp-interrupt` or `fi`, to pause the execution of the VM and return the control to the debugger (equivalent to the known sudo dtrace -w -n "BEGIN { breakpoint(); }"); +- `fdp-save` or `fs`, to save the current state of the VM; +- `fdp-restore` or `fr`, to restore the VM to the last saved state. + +In the debugger, use `help ` and ` -h` to see the command usage, like: +``` +(lldb) help fdp-attach + For more information run 'help fdp-attach' Expects 'raw' input (see 'help raw-input'.) + +Syntax: fdp-attach + + Connect to a macOS VM via FDP. + The VM must have already been started. + Existing breakpoints are deleted on attaching. + Re-execute this command every time the VM is rebooted. + +(lldb) fdp-attach -h +usage: fdp-attach [-h] vm_name + +positional arguments: + vm_name + +optional arguments: + -h, --help show this help message and exit +``` + +## Important notes +- As per current FDP limitations, set the macOS VM to use one CPU only and less or equal than 2 GB of RAM (in VirtualBox' settings) +- Do not connect multiple instances of LLDBagility to the same macOS VM at the same time +- If the macOS VM reboots (for any reason), redo `fdp-attach` (the kernel slide changes and LLDB is not aware of this) +- If debugging seems slow or intermittent, disable App Nap in the macOS host +- Pause the kernel execution before setting breakpoints or LLDB will complain +- Preferably load lldbmacros after attaching, otherwise the error `FATAL FAILURE: Unable to find kdp_thread state for this connection.` is raised (and some macros breaks) +- LLBDagility should work out of the box from XNU 4903.251.3 (the latest at the time of writing) to XNU 1486.2.11; before that, minor adjustments are required in `STUBVM.read_virtual_memory()` so that the fake `kdp` struct matches the one used by the kernel + +## Example session +``` +$ env PATH="/usr/bin:/bin:/usr/sbin:/sbin" lldb +(lldb) fdp-attach macos-mojave-18E226 +LLDBagility + Kernel load address: 0xffffff800d200000 + Kernel slide: 0xd000000 + Kernel version: Darwin Kernel Version 18.5.0: Mon Mar 11 20:40:32 PDT 2019; root:xnu-4903.251.3~3/RELEASE_X86_64 +Version: Darwin Kernel Version 18.5.0: Mon Mar 11 20:40:32 PDT 2019; root:xnu-4903.251.3~3/RELEASE_X86_64; stext=0xffffff800d200000 +Kernel UUID: 4170BF94-38B6-364F-A1B0-2F7C2C30F9A9 +Load Address: 0xffffff800d200000 +warning: 'kernel' contains a debug script. To run this script in this debug session: + + command script import "/Library/Developer/KDKs/KDK_10.14.4_18E226.kdk/System/Library/Kernels/kernel.dSYM/Contents/Resources/DWARF/../Python/kernel.py" + +To run all discovered debug scripts in this session: + + settings set target.load-script-from-symbol-file true + +Kernel slid 0xd000000 in memory. +Loaded kernel file /Library/Developer/KDKs/KDK_10.14.4_18E226.kdk/System/Library/Kernels/kernel +Loading 62 kext modules .............................................................. done. +kernel was compiled with optimization - stepping may behave oddly; variables may not be available. +Process 1 stopped +* thread #1, stop reason = signal SIGSTOP + frame #0: 0xffffff800d4c2fb6 kernel`pmap_pcid_activate(tpmap=0xffffff800dcc17e0, ccpu=, nopagezero=, copyio=) at pmap_pcid.c:343 [opt] +Target 0: (kernel) stopped. +(lldb) command script import "/Library/Developer/KDKs/KDK_10.14.4_18E226.kdk/System/Library/Kernels/kernel.dSYM/Contents/Resources/DWARF/../Python/kernel.py" +Loading kernel debugging from /Library/Developer/KDKs/KDK_10.14.4_18E226.kdk/System/Library/Kernels/kernel.dSYM/Contents/Resources/DWARF/../Python/kernel.py +. . . +xnu debug macros loaded successfully. Run showlldbtypesummaries to enable type summaries. +settings set target.process.optimization-warnings false +(lldb) showversion +Darwin Kernel Version 18.5.0: Mon Mar 11 20:40:32 PDT 2019; root:xnu-4903.251.3~3/RELEASE_X86_64 +(lldb) showbootargs +"fs4:\System\Library\CoreServices\boot.efi" usb=0x800 keepsyms=1 -v -serial=0x1 +(lldb) showproctree +PID PROCESS POINTER +=== ======= ======= +0 kernel_task [ 0xffffff800de15968 ] +|--1 launchd [ 0xffffff801456df10 ] +| |--11 kextcache [ 0xffffff801456daa0 ] +(lldb) c +Process 1 resuming +(lldb) fdp-interrupt +Process 1 stopped +* thread #3, name = '0xffffff8013da71d0', queue = '0x0', stop reason = signal SIGINT + frame #0: 0xffffff800d4def80 kernel`machine_idle at pmCPU.c:181 [opt] +Target 0: (kernel) stopped. +(lldb) showproctree +PID PROCESS POINTER +=== ======= ======= +0 kernel_task [ 0xffffff800de15968 ] +|--1 launchd [ 0xffffff801456df10 ] +| |--220 com.apple.Ambien [ 0xffffff80179d1d50 ] +| |--219 sharedfilelistd [ 0xffffff80179d21c0 ] +| |--218 CVMCompiler [ 0xffffff80179d2630 ] +| |--217 CVMServer [ 0xffffff80179d2aa0 ] +. . . +| |--40 uninstalld [ 0xffffff801456d1c0 ] +| |--39 wifiFirmwareLoad [ 0xffffff801456d630 ] +| |--37 UserEventAgent [ 0xffffff801456daa0 ] +| |--36 syslogd [ 0xffffff801456e380 ] +(lldb) showipcsummary +task pid #acts tablesize command +0xffffff8013d89cc0 0 94 21 kernel_task +0xffffff8013d8a840 1 4 1194 launchd +0xffffff8014e42b80 86 6 341 loginwindow +0xffffff8014e45980 37 5 512 UserEventAgent +0xffffff8014e425c0 39 2 42 wifiFirmwareLoad +. . . +0xffffff80179d6000 218 2 42 CVMCompiler +0xffffff80179d8e00 219 4 85 sharedfilelistd +0xffffff80179d93c0 220 4 85 com.apple.Ambien +Total Table size: 13619 +``` diff --git a/misc/create-mojave-iso.sh b/misc/create-mojave-iso.sh new file mode 100755 index 00000000..7f547024 --- /dev/null +++ b/misc/create-mojave-iso.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e +dirname () { python -c "import os; print(os.path.dirname(os.path.realpath('$0')))"; } +cd "$(dirname "$0")" + +hdiutil create -o "/tmp/macos-mojave.cdr" -size 6g -layout SPUD -fs HFS+J + +hdiutil attach "/tmp/macos-mojave.cdr.dmg" -mountpoint "/Volumes/macos-mojave-installmedia" +sudo "/Applications/Install macOS Mojave.app/Contents/Resources/createinstallmedia" --volume "/Volumes/macos-mojave-installmedia" + +hdiutil detach "/Volumes/Install macOS Mojave" +hdiutil convert "/tmp/macos-mojave.cdr.dmg" -format UDTO -o "/tmp/macos-mojave.iso" +mv "/tmp/macos-mojave.iso.cdr" "/tmp/macos-mojave.iso" diff --git a/misc/create-mojave-vdi.sh b/misc/create-mojave-vdi.sh new file mode 100755 index 00000000..628aaeaf --- /dev/null +++ b/misc/create-mojave-vdi.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -e +dirname () { python -c "import os; print(os.path.dirname(os.path.realpath('$0')))"; } +cd "$(dirname "$0")" + +# from https://github.com/AlexanderWillner/runMacOSinVirtualBox/blob/master/runMacOSVirtualbox.sh + +VBoxManage createhd --filename "macos-mojave.vdi" --variant Standard --size 61440 # brew cask install virtualbox + +EFI_DEVICE=$(vdmutil attach "macos-mojave.vdi" | grep "/dev"| head -n 1) # brew cask install paragon-vmdk-mounter + +diskutil partitionDisk "${EFI_DEVICE}" 1 APFS "macos-mojave" R + +diskutil mount "${EFI_DEVICE}s1" + +mkdir -p "/Volumes/EFI/EFI/drivers" >/dev/null 2>&1||true +cp "/usr/standalone/i386/apfs.efi" "/Volumes/EFI/EFI/drivers/" + +cat < /Volumes/EFI/startup.nsh +@echo -off +load "fs0:\EFI\drivers\apfs.efi" +#fixme bcfg driver add 0 "fs0:\\EFI\\drivers\\apfs.efi" "APFS Filesystem Driver" +map -r +echo "Trying to find bootable device..." +for %p in "System\Library\CoreServices" "macOS Install Data\Locked Files\Boot Files" "macOS Install Data" ".IABootFiles" "OS X Install Data" "Mac OS X Install Data" + for %d in fs1 fs2 fs3 fs4 fs5 fs6 + if exist "%d:\%p\boot.efi" then + #fixme: bcfg boot add 0 "%d:\\%p\\boot.efi" "macOS" + "%d:\%p\boot.efi" + endif + endfor +endfor +echo "Failed." +EOT + +diskutil unmount "${EFI_DEVICE}s1" +diskutil eject "${EFI_DEVICE}"