2

General way to store strings in NASM is to use db like msg: db 'hello world', 0xA. I think this this stores the string in bss section. So the string will occupy the storage for the entire program. Instead if we store it in the stack, it will be alive only during local frame. For small strings (less than 8 bytes), this can be done using mov dword [rsp] 'foo'. But for longer strings string has to be split and be stored using multiple instructions. So this would increase the executable size (I thought so).

So now, which is better in large programs with multiple strings? Are any of my assumptions made above are wrong?

Sourav Kannantha B
  • 1,271
  • 1
  • 5
  • 19
  • 2
    The bss section is for zero-initialising at runtime generally. Initialised data can go into the data section or text section. Or there may be a specific section for read-only data, too. Minor error in your question: Using a single dword write you can only initialise 4 bytes at a time, which is a string of up to 3 bytes if you want a zero terminator byte included. – ecm Mar 10 '21 at 19:33
  • Related re: efficiency of constructing strings on the stack (although in 32-bit mode): [Re-use string at known address to save bytes and reduce size of shellcode payload](https://stackoverflow.com/q/57469740) – Peter Cordes May 03 '22 at 08:40

2 Answers2

4

mov dword [rsp] 'foo' assembles to C70424666F6F00, it takes 7 bytes to encode 4 payload characters.
In comparison with standard static way DB 'foo',0 the definition of string as immediate operand in code section increases the code size by 75 %.

Your dynamic method may be profitable only if you could eliminate the .rodata or .data section entirely (which is seldom the case of large programs). Each additional section takes more space in executable program than its netto contents because of its file-alignment (in PE format it is 512 bytes). And even when your program has no other static data in data sections beside long strings, you could spare more space with static declaration in .text (code) section.

vitsoft
  • 4,456
  • 1
  • 14
  • 28
  • `mov rcx, imm64` + `push rcx` is 10+1 = 11 total bytes for 8 bytes of payload = 73% efficiency, vs. 4/7 = 57%. (At an offset from RSP it would be even worse, but to save code size you could use RBP+imm8 instead of RSP+imm8, but that's still 4 bytes per 7. You could mix `push imm32` with `mov dword [rsp+4], imm32` but that's also bad.) – Peter Cordes Mar 10 '21 at 23:39
  • 2
    Compilers will sometimes use immediate data to init a local struct or array that *has* to be on the stack anyway (i.e. they have to pass a non-const pointer to another function); their threshold for switching to copying from .rodata (with movdqa xmm load/store) is higher than 8 bytes. But when you just want to print the string, you just need to pass a pointer to .rodata without copying it to the stack at all, so the threshold is much lower for it to be worth it to use stack space. Maybe 8 bytes, maybe 16, maybe only 4, depending on I-cache vs. D-cache pressure in your program. – Peter Cordes Mar 10 '21 at 23:44
  • 2
    Of course, for just 4 bytes you'd use `push 'foo'` = 5 bytes = 80% efficiency. Getting the pointer into a register with `mov rdi, rsp` is smaller than `lea rdi, [rel msg_foo]` by 4 bytes, so it's an overall win for total size (unless padding for function alignment bumps it up or hides it). But anyway, comparing against the *best* option instead of the worst might be a good idea for your answer. – Peter Cordes Mar 10 '21 at 23:47
3

But for longer strings string has to be split and be stored using multiple instructions. So this would increase the executable size (I thought so).

Yep, and in almost all cases, the number of bytes used by those instructions will exceed the number of bytes that would be needed to just store the string in memory normally. (The instruction includes all the bytes of the immediate, with a few exceptions like zero- and sign-extension, as well as additional bytes to encode the opcode, destination address, etc). And code, of course, also occupies (virtual) memory for the entire duration of the program. There's no free lunch.

As such, you should just assemble the strings directly into memory with db as you have done. But try to arrange for them to go in a read-only section such as .text or .rodata depending on what your system supports. A modern operating system will then be able to discard those pages from physical memory if there is memory pressure, knowing that they can be reloaded from the executable if needed again. This is entirely transparent to your program. If there are many strings that will be needed at the same times, you can optimize a little by trying to arrange for them to be adjacent in your program's memory (defining them all together in one asm file should suffice).

You could also design something where at runtime, you read your strings from an external file into dynamically allocated memory, then free it when done with them. This was a common technique for programs on ancient operating systems with limited physical memory and no virtual memory support (think MS-DOS).

Nate Eldredge
  • 36,841
  • 4
  • 40
  • 60