Using GDB with MaNGOS as an Example

Imagine for a moment that you are the captain and mechanic of a huge spaceship, on which there are many passengers at the same time. But this is not an ideal ship, but a dilapidated vessel. Yes, a lot works, but much remains to be fixed. The question is, how? There would be such a tool that would allow us to disassemble our ship to the smallest bolt, to understand how it is arranged, what happens inside it when one button or another is pressed, to feel the essence, its soul. Moreover, it would be ideal to still be able to stop time at the time of fixing the problem, so that everything would be put on the shelves. After all, it is not so easy to understand what is happening inside!
Previously, I was afraid to get inside such a monster as mangos, and I used the debugger only when preparing an assignment on computer science at the institute. But it turned out that everything is much simpler and there is nothing to worry about, even though there is a lot of code. I will try to describe how using GDB to get inside the popular emulator of one MMORPG and look at its work from the inside. The main thing is not to be afraid to experiment, within reason. Those who know what GDB is will not find anything new for themselves here. This idea was prompted by the game by everyone of the “beloved” class as a paladin. Actually, a screenshot from the game:



What do we see? In the description of the spell, 152-172 damage, but in practice 232. Where did the extra damage come from? The GNU debugger helped me understand this.

Server assembly


To get started, let's build the server itself using cmake. Actually, everything is standard:
mkdir build
cd build
cmake ..
make -j4
make install

I deliberately did not build the server in debug mode, so that I would focus on the impossibility of debugging without installing the DEBUG = 1
variable. The first problems got out in the assembly. Cmake successfully prepared the makefiles, but errors got into the compilation, namely, ADAPTIVE Communication Environment (ACE) and TBB were not collected. Having pre-run Autotools, these libraries have gathered, but this is the way of the gentushnik. Why collect what is already collected and lies in the repository? As a result, we add the variables ACE_USE_EXTERNAL = 1 TBB_USE_EXTERNAL = 1
Great, in the cmake console I grabbed everything:
-- Found ACE library: /usr/lib/libACE.so
-- Found ACE headers: /usr/include
-- Found Intel TBB
-- Using mysql-config: /usr/bin/mysql_config
-- Found MySQL library: /usr/lib/libmysqlclient_r.so
-- Found MySQL headers: /usr/include/mysql
-- Found OpenSSL library: /usr/lib/libssl.so
-- Found OpenSSL headers: /usr/include/openssl
-- Found ZLIB: /usr/include

I told the linker to use libraries with the OS, rather than building them. While going to the kitchen for tea, everything gathered. We start the server under the debugger:
gdb ./mangos-world
GNU gdb (GDB) 7.0.1-debian
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/gabriel/projects/cpp/mangos-server/bin/mangos-world...(no debugging symbols found)...done.
(gdb) r

A few seconds passed, spam to the console about downloading the whole world and the inscription that the server started:
WORLD: World initialized
SERVER STARTUP TIME: 0 minutes 2 seconds
[New Thread 0x7fffea326700 (LWP 8911)]
[0 ms] SQL: UPDATE realmlist SET color = 0, population = 0, realmbuilds = '5875 6005 '  WHERE id = '1'
[New Thread 0x7fffe9a25700 (LWP 8912)]
engine: Max allowed socket connections 1024
[New Thread 0x7fffe9224700 (LWP 8913)]
Network Thread Starting
[New Thread 0x7fffe8a23700 (LWP 8914)]
Network Thread Starting

What is r ? This is run, that is, launch. Press Ctrl + C to return to the debugger console, not the server, and set the first breakpoint:
(gdb) b Unit::DealDamage
Can't find member of namespace, class, struct, or union named "Unit::DealDamage"
Hint: try 'Unit::DealDamage or 'Unit::DealDamage
(Note leading single quote.)
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (Unit::DealDamage) pending.
(gdb) c
Continuing.

b - breakpoint, telling the debugger where to stop, c - continue, continue
I made a breakpoint on the Unit :: DealDamage function, judging by the name, it uses the effects of spells, but this is purely an assumption, and it turned out to be true. By the way, the debugger supports autocompletion of functions, the TAB button helped) I was confused by the warning Make breakpoint pending on future shared library load? (y or [n]), basic knowledge of English helped to understand that the debugger offers to make a breakpoint depending on the load of the shared library (it’s hard to translate, but the unixoid is clear enough). Then I enter the game, create a paladin, teach exorcism and teleport to kill undead.



It's interesting, the breakpoint didn’t work! This is because I put together the server in the release. Add -DDEBUG = 1
Our world was going to take a little longer, with garbage in the console about type mismatch, but in the end it got ready. Again, run the debugger and set a breakpoint. Notice, now it completely loaded the server (there is no phrase no debugging symbols found ). And no swearing:
(gdb) b Unit::DealDamage
Breakpoint 1 at 0xb3d91a: file /home/gabriel/projects/cpp/mangos/src/game/Unit.cpp, line 479.

Picking inside the server


Move on.
We press exorcism and ... the game hangs, but in the console we have:
Breakpoint 1, Unit::DealDamage (this=0x133c000, pVictim=0x7fffe80f6080, damage=599, cleanDamage=0x7fffeaa4b680, damagetype=SPELL_DIRECT_DAMAGE, damageSchoolMask=SPELL_SCHOOL_MASK_HOLY, 
    spellProto=0x7fffefdb3010, durabilityLoss=true) at /home/gabriel/projects/cpp/mangos/src/game/Unit.cpp:479
479         if(pVictim != this)

Damage is already increased, 599, although less in the description. We look at the trace:
(gdb) bt
#0  Unit::DealDamage (this=0x133c000, pVictim=0x7fffe80f6080, damage=599, cleanDamage=0x7fffeaa4b680, damagetype=SPELL_DIRECT_DAMAGE, damageSchoolMask=SPELL_SCHOOL_MASK_HOLY, 
    spellProto=0x7fffefdb3010, durabilityLoss=true) at /home/gabriel/projects/cpp/mangos/src/game/Unit.cpp:479
#1  0x0000000000b410bd in Unit::DealSpellDamage (this=0x133c000, damageInfo=0x7fffeaa4b6f0, durabilityLoss=true) at /home/gabriel/projects/cpp/mangos/src/game/Unit.cpp:1331
#2  0x0000000000a21f66 in Spell::DoAllEffectOnTarget (this=0x7fffe7ee3d80, target=0x7fffe7f2bf80) at /home/gabriel/projects/cpp/mangos/src/game/Spell.cpp:1006
#3  0x0000000000a28912 in Spell::handle_immediate (this=0x7fffe7ee3d80) at /home/gabriel/projects/cpp/mangos/src/game/Spell.cpp:2804
#4  0x0000000000a286b2 in Spell::cast (this=0x7fffe7ee3d80, skipCheck=false) at /home/gabriel/projects/cpp/mangos/src/game/Spell.cpp:2769
#5  0x0000000000a29424 in Spell::update (this=0x7fffe7ee3d80, difftime=100) at /home/gabriel/projects/cpp/mangos/src/game/Spell.cpp:2982
#6  0x0000000000a34307 in SpellEvent::Execute (this=0x7fffe7f3bee8, e_time=5167, p_time=100) at /home/gabriel/projects/cpp/mangos/src/game/Spell.cpp:5992
#7  0x0000000000c77b8b in EventProcessor::Update (this=0x133c118, p_time=100) at /home/gabriel/projects/cpp/mangos/src/framework/Utilities/EventProcessor.cpp:34
#8  0x0000000000b3cd04 in Unit::Update (this=0x133c000, update_diff=100, p_time=100) at /home/gabriel/projects/cpp/mangos/src/game/Unit.cpp:300
#9  0x000000000096f66e in Player::Update (this=0x133c000, update_diff=100, p_time=100) at /home/gabriel/projects/cpp/mangos/src/game/Player.cpp:1124
#10 0x0000000000940bbb in WorldObject::UpdateHelper::Update (this=0x7fffeaa4bce0, time_diff=100) at /home/gabriel/projects/cpp/mangos/src/game/Object.h:404
#11 0x0000000000ae873c in Map::Update (this=0x2d14000, t_diff=@0x7fffe9a17e40) at /home/gabriel/projects/cpp/mangos/src/game/Map.cpp:446
#12 0x0000000000c5a8a6 in MapUpdateRequest::call (this=0x7fffe9a17e20) at /home/gabriel/projects/cpp/mangos/src/game/MapUpdater.cpp:61
#13 0x0000000000c76911 in DelayExecutor::svc (this=0x7fffeb8258d0) at /home/gabriel/projects/cpp/mangos/src/shared/DelayExecutor.cpp:57
#14 0x00007ffff7b77847 in ACE_Task_Base::svc_run(void*) () from /usr/lib/libACE-5.7.7.so
#15 0x00007ffff7b78bc1 in ACE_Thread_Adapter::invoke() () from /usr/lib/libACE-5.7.7.so
#16 0x00007ffff5e138ca in start_thread () from /lib/libpthread.so.0
#17 0x00007ffff5b7a86d in clone () from /lib/libc.so.6
#18 0x0000000000000000 in ?? ()

bt , as you probably already guessed, this is backtrace. Having studied the function calls a bit, I realized that it was necessary to monitor the change in the damageInfo variable in the Spell :: DoAllEffectOnTarget function. We set a breakpoint on this function, and delete the old, as well as extra breaks for items and GO:
(gdb) b Spell::DoAllEffectOnTarget
Breakpoint 2 at 0xa22e57: file /home/gabriel/projects/cpp/mangos/src/game/Spell.cpp, line 1257.
Breakpoint 3 at 0xa22cef: file /home/gabriel/projects/cpp/mangos/src/game/Spell.cpp, line 1230.
Breakpoint 4 at 0xa21668: file /home/gabriel/projects/cpp/mangos/src/game/Spell.cpp, line 860.
warning: Multiple breakpoints were set.
Use the "delete" command to delete unwanted breakpoints.
(gdb) d 1
(gdb) d 2
(gdb) d 3

The new hang of the game, we are inside the function, we run it line by line with the entry inside:
Breakpoint 4, Spell::DoAllEffectOnTarget (this=0x7fffe7ecfd80, target=0x7fffe7ef7f80) at /home/gabriel/projects/cpp/mangos/src/game/Spell.cpp:860
860         if (m_spellInfo->Id <= 0 || m_spellInfo->Id > MAX_SPELL_ID ||  m_spellInfo->Id == 32 || m_spellInfo->Id == 80)
(gdb) list
855         m_UniqueItemInfo.push_back(target);
856     }
857
858     void Spell::DoAllEffectOnTarget(TargetInfo *target)
859     {
860         if (m_spellInfo->Id <= 0 || m_spellInfo->Id > MAX_SPELL_ID ||  m_spellInfo->Id == 32 || m_spellInfo->Id == 80)
861             return;
862
863         if (!target || target == (TargetInfo*)0x10 || target->processed)
864             return;
(gdb) n 10
884         unitTarget = unit;
...
985                 caster->CalculateSpellDamage(&damageInfo, m_damage, m_spellInfo, m_attackType);
(gdb) print m_damage
$4 = 535
(gdb) s
Unit::CalculateSpellDamage (this=0x133c000, damageInfo=0x7fffeb24c6f0, damage=535, spellInfo=0x7fffefdb3010, attackType=BASE_ATTACK)
    at /home/gabriel/projects/cpp/mangos/src/game/Unit.cpp:1227
1227        SpellSchoolMask damageSchoolMask = GetSchoolMask(damageInfo->school);
...
1265                damage = SpellDamageBonusDone(pVictim, spellInfo, damage, SPELL_DIRECT_DAMAGE);
(gdb) s
Unit::SpellDamageBonusDone (this=0x133c000, pVictim=0x7fffe80f6080, spellProto=0x7fffefdb3010, pdamage=535, damagetype=SPELL_DIRECT_DAMAGE, stack=1)
    at /home/gabriel/projects/cpp/mangos/src/game/Unit.cpp:5345
5345        if(!spellProto || !pVictim || damagetype==DIRECT_DAMAGE )
...
5425        DoneTotal = SpellBonusWithCoeffs(spellProto, DoneTotal, DoneAdvertisedBenefit, 0, damagetype, true);
(gdb) s
Unit::SpellBonusWithCoeffs (this=0x133c000, spellProto=0x7fffefdb3010, total=0, benefit=0, ap_benefit=0, damagetype=SPELL_DIRECT_DAMAGE, donePart=true)
    at /home/gabriel/projects/cpp/mangos/src/game/Unit.cpp:5302
5302        if (GetTypeId()==TYPEID_UNIT && !((Creature*)this)->IsPet())
(gdb) n
5305        else if (SpellBonusEntry const* bonus = sSpellMgr.GetSpellBonusData(spellProto->Id))
(gdb) n
5307            coeff = damagetype == DOT ? bonus->dot_damage : bonus->direct_damage;
(gdb) n
5310            if (donePart && (bonus->ap_bonus || bonus->ap_dot_bonus))
(gdb) n
5312                float ap_bonus = damagetype == DOT ? bonus->ap_dot_bonus : bonus->ap_bonus;
(gdb) n
5314                total += int32(ap_bonus * (GetTotalAttackPowerValue(IsSpellRequiresRangedAP(spellProto) ? RANGED_ATTACK : BASE_ATTACK) + ap_benefit));
(gdb) print ap_bonus
$5 = 0.150000006
(gdb) n
5321        if (benefit)
(gdb) n
5336        return total;
(gdb) print total
$6 = 68
(gdb) n
5337    };
(gdb) n
Unit::SpellDamageBonusDone (this=0x133c000, pVictim=0x7fffe80f6080, spellProto=0x7fffefdb3010, pdamage=535, damagetype=SPELL_DIRECT_DAMAGE, stack=1)
    at /home/gabriel/projects/cpp/mangos/src/game/Unit.cpp:5427
5427        float tmpDamage = (int32(pdamage) + DoneTotal * int32(stack)) * DoneTotalMod;
(gdb) n
5429        if(Player* modOwner = GetSpellModOwner())
(gdb) print tmpDamage
$7 = 603
(gdb) c

In order not to bore you, I missed part of the console output as I monitored line by line the change in damage. I will describe only briefly the commands:
n - next, execute the next line of code without going inside
s - step, the same thing, just go inside the function
l - list, print a piece of code
p - print, output the variable.
As a result, I found out that the base damage was 535, as it should be in the description of the spell, and the additional damage is calculated in line 5314 of the Unit.cpp file and is 15% of the AP. Why is that? The mechanics of the game. In total, it turned out 603, which later flew out in the game.
Screenshot from the AP:



That's all, I hope that I briefly and clearly described the basics of working with the debugger under the nixes on a live project.

References


Debugging with GBD - a detailed description of GDB, Richard Stallman, Roland Pesh, Stan Shebs and others.
The Mangos project is a great place to learn c ++

UPD:
from mejedi :
“I was confused by the warning Make breakpoint pending on future shared library load? (y or [n]), basic knowledge of English helped to understand that the debugger offers to set a breakpoint depending on the loading of the shared library. ”

This message means that gdb did not find the function in which they wanted to set the breakpoint, and asks: can I postpone the installation of the breakpoint? If you agree, then when loading each new dynamic library (shared library) gdb will try to find this function there, and if successful, set a breakpoint.

Also popular now: