{"id":20134569,"url":"https://github.com/gnebbia/gdb_tutorial","last_synced_at":"2026-03-05T16:06:56.879Z","repository":{"id":129498527,"uuid":"142748975","full_name":"gnebbia/gdb_tutorial","owner":"gnebbia","description":"Some notes on how to use gdb to do some binary analysis and debugging","archived":false,"fork":false,"pushed_at":"2019-10-02T22:17:29.000Z","size":17,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-13T09:27:27.145Z","etag":null,"topics":["debugging","debugging-tool","gdb","guide","notes","tutorial"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gnebbia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-29T09:49:12.000Z","updated_at":"2022-04-22T08:43:47.000Z","dependencies_parsed_at":"2023-04-05T03:17:15.807Z","dependency_job_id":null,"html_url":"https://github.com/gnebbia/gdb_tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gnebbia%2Fgdb_tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gnebbia%2Fgdb_tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gnebbia%2Fgdb_tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gnebbia%2Fgdb_tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gnebbia","download_url":"https://codeload.github.com/gnebbia/gdb_tutorial/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241581157,"owners_count":19985707,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["debugging","debugging-tool","gdb","guide","notes","tutorial"],"created_at":"2024-11-13T21:10:00.440Z","updated_at":"2026-03-05T16:06:56.308Z","avatar_url":"https://github.com/gnebbia.png","language":null,"readme":"# GDB Tutorial\n\n## Introduction\n\nDebugging is the art and science of finding and eliminating bugs \nin software. Bugs can be simple functional issues or in some cases have \nsecurity implications.\ngdb is a famous debugger included in many UNIX or UNIX-like systems.\nLet's see in these notes, how to use gdb properly in order to effectively debug\nsoftware.\n\nIn order to open a program with gdb we do:\n```sh\n gdb ./programName\n # opens programName with gdb\n```\n\nOnce we are in GDB we can run the program by doing: \n```sh\n run parameter1 parameter2 \n # in this case we run the program passing the specified parameters\n```\n\nif the program was compiled with the so called \"debugging symbols\", we will will\nhave useful information about variables, functions, and other stuff.\nDebug symbols can either be integral part of the binary file or can be placed \nin a separate file.\n\nWe can disassemble a program by doing:\n\n```sh\n disassemble main\n # this will disassemble main, an arrow drawn like \"=\u003e\" will show what EIP\n # is pointing to, so what is the next instruction which will be executed\n```\n\nwe can attach gdb to a running process by doing:\n```sh\n attach pid\n # attaches GDB to the specified process ID\n```\n\nwe can also attach to an existing process by launching gdb in quiet mode, so\nthat the boilerplate initial information does not appear in the output by doing:\n```sh\ngdb -q -p \u003cPID\u003e\n```\n\nif the program crashes we can run:\n```sh\n backtrace\n # show the current stack, an alias is \"bt\"\n```\n\n\n### Debugging Symbols\n\nWe need to be explicitly mention the intention to create debug \nsymbols at compile time, we have different kind of debug symbol \nfile types:\n\n* DWARF 2\n* COFF\n* XCOFF\n* Stabs\n\nwe have two options with gcc in order to compile with debug \nsymbols:\n\n* \"-g\" flag: in order to compile a program with debug symbols \n  with a format taken from the Operating System;\n* \"-ggdb\" flag: in order to compile a program with GDB specific \n  debug symbols , these are the best one understood by gdb.\n\nIn a practical compilation scenario we can include debug symbold by doing:\n```sh\n gcc -ggdb programName.c -o programName\n```\n\n### What Symbol Files tell us ?\n\n\nThe information provided by debug files and debug symbols can be summarized as\nfollows:\n\n- info on **sources**: so we will have the source code available, \n    done with \"list 1\" or \"list\", or we can see the source file \n    name with \"info sources\"\n    N.B.: if we rename or delete the source code file/files we won't \n    be able to list source code\n- info on **functions**: list of functions available with relative name of the source,\n    we can do this with \"info functions\";\n- info on **(global) variables**: list of variables available, we can do this\n    with \"info variables\", this will list only global variables;\n    Notice that in order to get info on local variables we have to specify \n    the scope, we can do this by with \"info scope\" + tab tab will give us the list\n    of available scopes and we do for example \"info scope functionName\"\n    or \"info scope main\".\n\nIn order to detach (or strip) symbols from a binary file and save them in a separate \nfile we can do:\n```sh\n objcopy --only-keep-debug \u003cBinaryFile\u003e \u003cOutputDebugFile\u003e\n # we will put debug symbols of the program binaryFile in the file \n # called debugFile, without deleting them from the binary\n```\n\nwe can attach debug symbols to a binary by doing:\n```sh\n objcopy --add-gnu-debuglink=\u003cInputDebugFile\u003e \u003cBinaryFile\u003e\n # this will add the debug symbol file called \"InputDebugFile\"\n # to the binary program called \"BinaryFile\"\n```\n\nwe can simply strip debug symbols from a binary by doing:\n```sh\n strip --strip-debug \u003cBinaryName\u003e \n # this will delete debug symbols from \u003cBinaryName\u003e\n```\n\nAnyway, remember that also after doing this removal operation there will still\nbe additional informations that can be used by reverse engineers.\n\nWe can delete everything which is unnecessary by doing:\n```sh\n strip --strip-debug --strip-unneeded \u003cProgramName\u003e\n #this will delete all the debug symbols, we won't see even the function names\n```\n\nso in order to not let other view my code and make the \nlife of reverse engineers harder we can use the second strip command, \nthis will also make the binary smaller.\n\nSo summarizing with debugging symbols we can:\n- `list 1`, list source code\n- `info sources`, list information on the source file, such as name, and possibly other informations\n- `info functions`, list functions\n- `info variables`, list flobal variables\n- `info scope \u003cFunctionName\u003e`, list local variables in the specified function\n- `info files`, lists all the sections and their addresses, like \".text\", \".bss\", \".data\", etc...\n\n\n\n#### Loading a symbol file in GDB\n\n\nIn order to load a symbol file in GDB we do:\n```sh\n symbol-file fileName \n # this command inside gdb will load the symbol file called \"fileName\"\n```\n\n## Inspecting Symbols with \"nm\"\n\nThe program \"nm\" will list symbols contained in an object file, so we can \ninspect symbols related to a file by doing:\n```sh\n nm ./programName \n # this will list all the symbols of the program \"programName\"\n```\n\nBy default the list will be composed by 3 rows where:\n\n1. the first, represents the virtual address;\n2. the second, denotes the symbol type;\n3. the third, denotes the symbol name.\n\n\n### Symbol Types\n\nFor symbol types we have:\n\n\n| Symbol Table |                 Meaning                 |\n|:------------:|:---------------------------------------:|\n|       A      |             absolute symbol             |\n|       B      | in the uninitialized data section (BSS) |\n|       D      |     in the initialized data section     |\n|       N      |             debugging symbol            |\n|       T      |           in the text section           |\n|       U      |        symbol undefined right now       |\n\n\nWe moreover can encounter both \"uppercase\" or \"lowercase\" symbols:\n\n* lowercase symbol is a \"local\" symbol\n* uppercase symbol is an \"external\" symbol\n\nfor a complete list of symbols we do `man nm`.\n\n### Other nm examples\n\n```sh\n nm -a | grep functionName\n```\n\n  -- this can be useful if we want to find a specific function to \n    which executable is owned\n\n  -- this can be useful even by specifying a specific symbol type \n    instead of function name\n\n```sh\n nm -n #display all the symbols in sorted order\n```\n```sh\n nm -g #displays all the external symbols\n```\n```sh\n nm -S #displays the size of the corresponding object for each \n  symbol\n```\n\n\n## Strace\n\nThe \"strace\" tool will help us understand how our program \ninteracts with the OS, this tool traces all system calls made by \nthe program, it even tells us about arguments passed and has \ngreat filtering capabilities. Let's see some examples:\n\n```sh\n strace ./programName 20 30\n```\n\n```sh\n strace -o report.txt ./programName \n # in this case with the option \"-o\" we store the output to another file\n```\n\n```sh\n strace -t ./programName \n # this will save the time at which each system call is called\n```\n\n```sh\n strace -r ./programName \n # this will save the relative time taken by each system call,\n # this can be helpful even to understand which system call takes \n # more time and which takes less time\n```\n\n```sh\n strace -r -e write ./programName \n # this will filter the output by putting only the system call \"write\"\n```\n\n```sh\n strace -e connect nc www.google.it 80 \n # this will filter the output by putting only the system call \"connect\"\n```\n\n```sh\n strace -e send,recv nc www.google.it 80\n # this will filter the output by putting only the system calls\n # \"send\" and \"recv\"\n```\n\nWe can even attach \"strace\" to a running process:\n\n```sh\n strace -p processID\n # in this case we attach strace to a running process, we have to specify\n # the process ID we want to attach to\n```\n\nwe can get even list and statistics of system calls used, in this \ncase we do:\n\n```sh\n strace -c ./programName\n # this allows to print statistics on system calls and let us understand\n # which system call a program is using, for example if we see \n # \"send\", \"connect\", etc... we understand that this is a program \n # which communicates over the network\n```\n\nall of this is important because when we are in GDB we can set \"breakpoints\"\non one of these system calls.\n\n\n## Breakpoints, Registers and Memory\n\nA breakpoint is a technique used to \"pause\" the program during \nexecution based on certain criteria, these criteria can be for \nexample \"before to execute a specific instruction\" (which we want \nto examine).\n\n### Breakpoints\n\n\nThere are different ways to set a breakpoint:\n\n* breakpoints on functions\n  * `break functionName` : will set a breakpoint on the function \n* breakpoints on instruction addresses\n  * `break *0x080484cd` : will set a breakpoint on an instruction \n    address, for example as shown by \"disassemble main\", we need \n    the asterisk before the address\n* breakpoint on line number\n  * `break 54` : will set a breakpoint on line number 54\n\nonce a breakpoint is set, we can run the program with:\n\n```sh\n run param1 \n # this will run the program, using as parameter \"param1\"\n```\n\nonce the program is frozen we can inspect various informations, \nfor example using:\n\n```sh\n info registers\n```\nor with:\n\n```sh\n info all-registers \n # this will dump all registers to screen\n```\n\nwe can see the list of active breakpoints with:\n\n```sh\n info breakpoints\n # this will list breakpoints\n```\n\nwe can enable/disable breakpoints once we have seen the id number \nby doing:\n\n```sh\n disable 1 \n # this will disable the breakpoint with id number equal to 1\n```\n\n```sh\n enable 1\n # this will enable breakpoint 1\n```\n\n```sh\n delete 1\n # this will delete breakpoint 1\n```\n\nwe can continue the execution after a breakpoint with the \ninstructions:\n\n```sh\n continue\n # continues until the end or until the next breakpoint (if there are any)\n```\n\n```sh\n step \n # will execute and point to the next line of C code, we can pass an argument\n # \"n\" to specify the number of code lines to step\n```\n\n```sh\n stepi\n # steps one assembly instruction exactly, we can pass an argument \"n\" to\n # specify the number of code lines to step\n```\n\n```sh\n finish\n # run until the end of the current function\n```\n\n```sh\n advance \u003clocation\u003e \n # in this case we advance to a specific location, which can be \"somefunction\"\n # or \"5\" (a line number) or \"hello.c:23\" a line number in a specific file\n```\n\n#### Conditional Breakpoints\n\n\nSometimes we want to use breakpoints only if certain conditions \nare met, these conditional breakpoints could be very handy for \nexample in case of loops. Let's see some example, let's say we \nput a breakpoint on line \"10\" of our code for example with:\n\n```sh\n break 10\n # set a breakpoint at line 10\n```\nwe can now view its id number with:\n\n```sh\n info breakpoints\n```\nnow let's say our breakpoint has as id number \"1\" we can do:\n\n```sh\n condition 1 counter == 5 \n # in this case we are telling GDB to \n # breakpoint (at line 10) only when the variable counter is equal \n # to 5\n```\n\nlet's see another example:\n\n```sh\n break *0x08048478 \n```\n\n```sh\n condition 1 $eax != 0\n # this will stop the program only when $eax is different from zero at the \n # instruction specified at the address mentioned in the break instruction\n```\n\n#### Watch Breakpoints\n\n\nWe can even set breakpoints when certain events happen, for \nexample when a variable is written/read or written or read from \nit, let's see some example:\n\n```sh\n watch variable1 \n # in this case we break when a variable is written to\n```\n\n```sh\n rwatch variable1\n # in this case we break when a variable is read from\n```\n\n```sh\n awatch variable1\n # in this case we break when a variable is written to or read from\n```\n\n```sh\n info watch\n # is the same thing as info break\n```\n\n### Examine Memory\n\n\nIn order to examine memory we use the commands: \n\n* x: to examine memory\n* print: which is useful to print variables\n* display: it is useful to look at the evolution of a certain variable\n\nthe \"x\" command has a very specific format which has to follow, \nthe general format is:\n\n```sh\n x/\u003cnumber\u003e\u003csuffix\u003e\n # where instead of \"number\" we specify the numerical quantity of data, \n # and with \"suffix\" we specify actually the suffix\n```\nLet's make some example:\n\n```sh\n x/s argv[1]\n # this will print the string argv[1]\n```\n\n```sh\n x/i $eip \n # this will show which instruction is the register eip pointing to\n```\n\n```sh\n x/10i $eip \n # this will show which instruction is the register eip pointing to \n # and additional following 9 instructions (in total 10 instructions)\n```\n\n```sh\n x/10xw $esp \n # this will show 10 words (32bit) in hexadecimal format starting from\n # the esp register\n```\n\n```sh\n x/10xg $rsp \n # this will show 10 giants (64bit) in hexadecimal format starting \n # from the rsp register\n```\n\n```sh\n x/5i $pc\n # shows the next 5 assembly instructions, this is useful to avoid doing \n # everytime \"disassemble main\" in order to inspect the code, \n # another common instruction is \"layout asm\"\n```\n\n```sh\n print argv[0]\n # this will print the argv[0] variable\n```\n\nNotice that \"print\" is equivalent to \"x/s\".\n\nLet's see an application of \"display\" and \"undisplay\":\n\n```sh\n display variable1\n # in this case at every step we print out the value of the variable \"variable1\"\n```\n\n```sh\n disp/i $pc \n # this will show the next instruction at each step, useful if combined \n # with stepi\n```\n\nwe can look at the current displays at:\n\n```sh\n info display\n```\n\nand we can undisplay with:\n\n```sh\n undisplay 1\n # where \"1\" in this case is the id number of the interested display\n```\n\n#### Modify Memory\n\n\nLet's take an example, in which we take start a program with gdb \nand want to modify some content of the memory. We would do:\n\n```sh\n gdb ./programName AAAA 10 20 \n # in this case our program let's say for the sake of the example \n # that takes 3 arguments as inputs\n```\n\n```sh\n break main\n # set the breakpoint to main\n```\n\nnow we can for example look at one of the passed arguments with:\n\n```sh\n x/s argv[1]\n```\n\nor view it character by character with:\n\n```sh\n x/5c argv[5] \n```\n\nthe above instructions will show us the starting memory address \nof the argument, we can change it by doing:\n\n```sh\n set {char} 0xbffff7e6 = 'B'\n # in this example we have put the starting memory address of argv[0] \n # and put it in a char (1Byte) the character \"B\" we will see a number \n # the next time in memory at that address, that number is the ascii code \n # of 'B', notice that we can view the ascii table by doing \"man ascii\"\n```\n\nalternatively we can achieve the same thing by inserting directly \nthe ascii code of 'B', for example:\n\n```sh\n set {char} 0xbffff7e6 = 66\n```\n\nwe can even set more bytes at a time, for example:\n\n```sh\n set {int} 0xbffff7e6 = 12\n # in this example we will have the first char as 12, but the following \n # set at zero, since the int takes more bytes\n```\nnow if we want to write at consecutive memory addresses (e.g., we \nwant to put all B's in the same memory address and following 3 \nbytes) we do:\n\n```sh\n set {char} 0xbffff7e6 = 'B'\n```\n\n```sh\n set {char} (0xbffff7e6 + 1) = 'B'\n```\n\n```sh\n set {char} (0xbffff7e6 + 2) = 'B'\n```\n\n```sh\n set {char} (0xbffff7e6 + 3) = 'B'\n```\n\nwe can even change the value of other variables, for example \nlet's suppose there is a variable called \"sum\" and we want to \nchange its value, in this case we would do:\n\n```sh\n set sum = 2000 \n # this will set the value of the variable called \"sum\" to 2000\n```\n\nwe can even change the value of registers:\n\n```sh\n set $eax = 10 \n # this will set the value of the register eax to 10\n```\n\n```sh\n set $eip = 0x80484c4 \n # this will set the instruction pointer to the address mentioned, \n # we took this address by making \"print functionName\" in this way we know at \n # which address the function is, it's very probable that this will make a \n # \"segmentation fault\" since when the function will terminate, \n # it will return to the address specified at the top of the stack, so it's very\n # probable that at the stack there is garbage, since we forced \n # the loading of an arbitrary function so when the function will \n # terminate we will have a segmentation fault, there is an \n # exception to this, in case the function calls the \"exit()\" system call\n```\n\n### Modify and Patch Binary\n\n\nWe can rewrite a binary file in multiple ways, for example by \nlaunching gdb with the option \"--write\" or by executing specific \ncommands inside gdb, let's see both examples:\n\n```sh\n gdb --write -q ./a.out \n # in this case we run the binary a.out with write permissions, \n # the flag \"-q\" it's used to suppress introductory messages and copyright \n # notice, so it's a \"quiet mode\"\n```\n\nor if we launch gdb without the option \"--write\" we can execute:\n\n```sh\n set write on \n # in this case we set the binary in write mode, so if we rewrite instructions\n # the binary will be modified, it is always a good idea to make a copy \n # of our binary before doing this\n```\n\n```sh\n set write off \n # in this case we disable the write mode, so the binary becomes again read-only\n```\n\n```sh\n show write \n # displays whether executable files and core files are opened \n # for writing as well as reading\n```\n\nonce in write mode we can put:\n\n```sh\n set {unsigned char}0x00000000004004b9 = 22\n # modifying part of the binary\n```\n\n## Convenience Variables and Routines\n\nWe can create variables in GDB to hold data, these variables are \ncalled \"convenience variables\", and can be set through the \"set\" \ncommand, let's see some example:\n\n```sh\n set $i = 10\n```\n```sh\n set $dyn = (char *)malloc(10)\n```\n\n  -- we can do this only after the program is running, and we \n    could inspect this for example by doing \"x/10xb $dyn\" that \n    will show us the first ten bytes starting from $dyn, so the \n    content of \"dyn\"\n\n```sh\n set $demo = \"joe\"\n```\n\n```sh\n set argv[1] = $demo\n```\nwe can even call C routines for example:\n\n```sh\n call strcpy($dyn, argv[1])\n # in this case we are copying the content of argv[1] into $dyn,\n # we can inspect $dyn by doing \"x/10xb $dyn\" or \"x/10c $dyn\"\n```\n\nwe can even call local functions listed with \"info functions\", \nfor example we can do something like:\n\n```sh\n call AddNumbers(10,20)\n # this will print on the screen the result given by the function\n```\n\n```sh\n call AddNumbers($i, $j)\n # this will print on the screen the result given by the mentrioned function,\n # notice that this time we passed as arguments two convenience variables\n```\n\nNotice that when we start a program and want to check different \nfunctions what we do is usually setting a breakpoint at \"main\" \nand then use the \"call\" instruction.\n\n## Window Commands\n\nWe can run gdb in a windowed TUI mode with:\n\n```sh\n gdb ./programName -tui\n```\nfrom here we can list windows and do other operations:\n\n```sh\n info win\n # we list windows\n```\n\n```sh\n focus winname \n # set focus to a particular window by name or position \"next\" or \"prev\",\n # we can even use \"fs\" as alias for \"focus\"\n```\n\n```sh\n layout type\n # where the layout type can be \"asm\", \"src\", \"split\" or \"reg\"\n```\n\n```sh\n layout asm \n # very useful for navigating assembly source code\n```\n\n```sh\n tui reg typ\n # set the register window layout \"general\", \"float\", \"system\" or \"next\"\n```\n\n```sh\n winheight val\n # set the window height to the chosen value, an alias is \"wh\"\n```\n\n## Practical Scenarios\n\n### Practical Scenario: Cracking a Simple Binary with Debug \n\n#### Symbols\n\nWe have a program that wants us to insert the secret password as \nparameter, in a poorly written code what we could simply do is:\n\n```sh\n strings programName \n # the \"strings\" program will show the strings contained in the binary\n```\nthis can show us sometimes interesting strings about the program \nin our case for example there were interesting strings and we \ntried one of those to crack the program. Poorly coded programs \nmay reveal private/secret informations. Secrets can be easily \nhidden by encryption/encoding. \n\nWe do some inspection generally with:\n\n```sh\n info functions\n```\n```sh\n info variables\n```\n```sh\n info scope interestingFunction\n```\n```sh\n break main \n # this is followed by some \"call interestingFunction()\"\n```\n```sh\n print interestingVariableName\n```\n### Practical Scenario: Disassembling and Cracking a Simple \n\n####  Binary\n\nHere we deal with assembly, now there are two main syntaxes:\n\n* AT\u0026T\n* Intel\n\nGDB as the GNU as assembly uses by default the AT\u0026T syntax. We \ncan change the assembly syntax by doing:\n\n```sh\n set disassembly-flavor intel \n # here we cahnge the syntax to intel, but we can check the \n alternatives by just pressing \"tab-tab\" after \"disassembly-flavor\"\n```\n\nIn this case what we do is:\n\n```sh\n disassemble main \n # here we will see the assembly instructions of our program\n```\n\nwe always have to remember that the assembly code we see is \ndependant on the architecture for which the program was written, \nfor example in a qemu virtual machine with debian armel \napplication we would see arm assembly code, while on an x86_64 \narchitecture we see an x86_64 assembly code.\n\n### Practical Scenario 2\n\n\n```sh\n layout asm\n```\n```sh\n disp/i $pc\n```\n```sh\n stepi\n```\n\n#### Assembly Tips\n\n```sh\n printf '#include \u003cstdio.h\u003e\\nint main(void) { printf(\"Hello, world!\\\\n\");\n return 0; }\\n' | gcc -x c -S - -o - -fno-asynchronous-unwind-tables\n```\n\nwhere:\n\n* `-x c` selects C as it cannot be determined otherwise when getting \n  source code from standard input \n* `-S` tells the compiler to stop compilation after generating the assembly,\n  but before assembling it \n* `-` means standard input \n* `-o` means write to standard output \n* `-fno-asynchronous-unwind-tables` disables special exception \n  suggest that generates lots of noise like .cfi_offset \n\nIf you do this, then you get a minimal hello world program in \nassembly, which uses the standard library You can write your own \n\".s\" file and assemble it. Use -m32 if you're on a 64-bit machine \nand want to try 32-bit assembly.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgnebbia%2Fgdb_tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgnebbia%2Fgdb_tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgnebbia%2Fgdb_tutorial/lists"}