Half-Life Programming - Debugging Last edited 4 months ago2020-02-21 05:34:53 UTC

Half-Life Programming

Programming is hard, and programming with the Half-Life SDK is harder. Sooner or later, something will go wrong, and you'll have no idea why. What can you do about that? The answer is, of course, debugging!

Getting Half-Life to debug properly takes a little bit of set up, so this article will guide you through what you need to do to get everything working.
This isn't a tutorial on how to use Visual Studio or GDB - only how to set up the Half-Life SDK for those tools. For information on how to use the debuggers, take a look here:

Debugging on Windows using Visual Studio

This method is recommended for most users, as VS makes debugging very convenient and easy. Assuming you have VS already set up and your mod is running, there's two extra things you need to do to get debugging working properly.

Copy DLL files on build

In Visual Studio, right click on your hldll project and select Properties. Expand Build Events and select Post-Build Event. The easiest way to deal with this is to get the post-build event to run a custom batch file on completion. If you're using the halflife-updated repository, enter this script into the Command Line option (drop down the option and select <Edit...> for a multi-line editor):
cd /D "$(ProjectDir)..\.."
cmd /c copy-server.bat
EXIT /B 0
User posted image
Do the same for the hl_cldll project, except change the batch file to copy-client:
cd /D "$(ProjectDir)..\.."
cmd /c copy-client.bat
EXIT /B 0
These commands will simply run the corresponding batch files in the root folder of your repository. Next, we'll create those files.

Go to the root folder of your repository and create the copy-server.bat file. In this example, that file will be located at D:\Github\halflife-updated\copy-server.bat. The contents of that file should be a command to copy the hl.dll file to your mod's location. Here's an example, but you must modify it to match the location of your mod, or it won't work properly!
robocopy ".\projects\vs2019\Debug\hldll" "C:\Program Files (x86)\Steam\SteamApps\common\Half-Life\testmod\dlls" hl.dll /njh /njs /ndl /nc /ns /np
Do the same thing for the copy-client.bat file. Here's an example of the contents:
robocopy ".\projects\vs2019\Debug\hl_cdll" "C:\Program Files (x86)\Steam\SteamApps\common\Half-Life\testmod\cl_dlls" client.dll /njh /njs /ndl /nc /ns /np
If your project has already been compiled, you should run these batch files now to make sure they copy the files into the right location. Take note that the Debug folder is hard-coded in these files. For the most part, that's fine - but when you release the final version of your mod, you should consider doing a Release build, which will put the compiled DLLs into a different spot. You cannot debug a Release build, so only change the build configuration once you're satisfied that everything is working.
Remember: You must update the batch files so that it matches the actual location of your mod! Do not just copy this text without modifying it.

Set up the debugger commands

First, choose a project to use as your "startup project". It doesn't matter which one you choose - so I'll assume you'll be using the hldll project. Right click on that project, and choose Set as StartUp Project. Then, right click it again and choose Properties. Select the Debugging section.

The important options here are Command and Command Arguments, and you should also set Working Directory as well. Here's what you should set them to: Here's what it should look like:
User posted image

Run the project

You should be all done now - click the green "play" button in VS, or press F5 to run the project. Assuming everything is set up properly, the code will be compiled, the post-build event will copy the DLLs into your mod, and then VS will launch Half-Life and start debugging it. Try placing a breakpoint on void CWorld :: Spawn( void ) or void CHud :: Init( void ) to make sure debugging is working on both the server and client projects respectively.

Debugging on Linux using GDB

Note: in the examples below, replace <username> with your Linux username.

If you like, you can use a shell script or update the makefile in order to copy the client and server .so files to your mod directory - but this isn't mandatory since on Linux, the compiler and the debugger processes are entirely separated. This guide assumes that you've already compiled your project and copied the .so files into the correct places.

Setting up a terminal

Open a terminal and change directory to the Half-Life game installation directory. This is typically located under your user directory with this path: /home/<username>/.steam/steam/steamapps/common/Half-Life. If you want to browse to this directory using a Files window or similar interface you may need to enable the Show hidden files option first to see the .steam directory.

Setting the library paths

Open a terminal and enter the following:
export LD_LIBRARY_PATH=/home/<username>/.steam/steam/steamapps/common/Half-Life:/home/<username>/.steam/bin32/steam-runtime/usr/lib/i386-linux-gnu:/home/<username>/.steam/bin32/steam-runtime/lib/i386-linux-gnu
This will set the library paths to search to the directory containing the game executable and the Steam runtime directories containing libraries used by Half-Life 1.

Launching the game

Now enter the following commands in your terminal:
gdb

file hl_linux
set args -dev -console -w 1280 -h 720 -game testmod
run
See the Windows/VS section for an explanation of the launch arguments - you can change them as needed. This will launch Half-Life with the specified mod - be sure to change it to the actual name of your mod folder.

Debugging "could not load library client" errors

Sometimes when developing a mod you will encounter "could not load library client" errors during startup, preventing you from testing or debugging your mod at all.

When this happens it typically means your mod's code references dependencies that can not be found, such as missing libraries or undefined variables and functions. The Half-Life engine does not report the error string for this kind of errors so it is difficult to diagnose.

To help find out what's happening, follow these steps:

On Windows

1. Download this tool: https://github.com/Solokiller/NativeDllTester/releases. This is a small tool to get the error code returned by LoadLibrary when it fails to load a library.

2. Place the executable in the Half-Life directory. This ensures that it will resolve paths the same way that the engine does.

3. Launch the tool. Enter the path to the dll or click Browse to select it, then click Load.

4. If an error occurs, the Error Code field contains the native error code. The translated error message is displayed in the field below that.

5. See the List of Error Codes to find the corresponding name and description in English. This should help you to pinpoint the cause of the problem that's causing it to fail to load.

On Linux

1. Start by setting up GDB by following the instructions above. If you try to launch your mod in GDB, you will notice that it fails to load there as well.

2. To figure out what's causing the problem, instead of running your mod, enter these commands after the file hl_linux command:
set args -dev -console -w 1280 -h 720 -game valve
run
Note the -game valve argument - this will launch Half-Life itself, using the unmodified default binaries. We need this because we're going to need an active process instance to execute some code through GDB.

3. Switch to the terminal with your GDB instance and press the button combination CTRL+C. This will interrupt execution and allow us to execute code in GDB directly. Now enter these commands (be sure to replace <mod directory name> with the name of your mod directory):
call (void*) dlopen("<mod directory name>/cl_dlls/client.so", 2)
call (char*) dlerror()
The first command calls the function dlopen with the path to the mod client library. The second parameter is the constant RTLD_NOW which tells dlopen to load the library and resolve all undefined symbols. By using this parameter any symbols that cannot be found in any other libraries will cause the library to fail to load, and the missing symbol will be returned as part of the error string.

The second command calls the dlerror function. This function returns the error that occurred during the last dlopen call, if any errors occurred. If your library fails to load this will be a string explaining what went wrong.

For example, if a global variable is declared but not defined you will get an error string that looks like this:
$2 = 0x8ddf5b0 "<mod directory name>/cl_dlls/client.so: undefined symbol: <variable name>"
With this information you should be able to track down and fix the cause of the problem.
Note: the (void*) and (char*) used before the function name are needed because GDB needs to know the return type of the functions in order to print the results. These are C-style casts. If you omit them GDB will not be able to execute the functions and reports an error instead.
For more information about the dlopen and dlerror functions, consult the documentation.

Comments

You must log in to post a comment. You can login or register a new account.