Worlds Simplest Bootloader :: Bare Metal Programming Series 4

Поделиться
HTML-код
  • Опубликовано: 7 фев 2025
  • In this episode of the bare metal programming series, we're taking our first steps into building a fully fledged bootloader! To do this, we need to split the application in two, build some supporting scripts, learn how to use linker scripts, and understand some of the internal CPU registers for relocating the interrupt vector table at runtime.
    =[ 🔗 Links 🔗 ]=
    🎥 Series Playlist: • Blinky To Bootloader: ...
    🗣 Discord: / discord
    ⭐️ Patreon: / lowleveljavascript
    💻 Github Repo: github.com/low...

Комментарии • 48

  • @weshowl7690
    @weshowl7690 24 дня назад +1

    Big help to my understanding. BTW: In a linker map, BSS stands for Block Started by Symbol. It refers to a section of an object file or executable that contains statically allocated variables that are declared but not yet assigned a value. These variables are typically initialized to zero by the operating system or the runtime environment before the program starts executing.
    The BSS section helps reduce the size of the object file, as it only stores the length of the BSS segment, not the actual data. When the program is loaded into memory, the BSS segment is allocated and initialized to zero.

  • @TimDrogin
    @TimDrogin Месяц назад

    Casting region of memory to a struct(vector table) was probably the most awesome use of c as a language I have ever seen. So clever and elegant in a way!

  • @stevezhou184
    @stevezhou184 11 месяцев назад +3

    like your video so much. bootloader is flying everywhere, but nobody explains exactly what "bootloader" is. This is problem of software, everyone coins their own concept, then use the popular NAME to name it. like your clearness

  • @AbidAli-mj8cu
    @AbidAli-mj8cu 10 месяцев назад +1

    At @46:51, the first 4 bytes represents Main stack pointer, which is end of the RAM (from where stack starts), 0x20000000U + 0x18000U (96KB) = 0x20018000U

  • @nhanNguyen-wo8fy
    @nhanNguyen-wo8fy 10 месяцев назад +1

    10:30 flash memory
    15:50 fix make file
    17:10 what bootloader do
    50:40 main
    59:30 vector table offset register

  • @ivsuk
    @ivsuk Год назад +3

    Thank you very much. Very clear and articulate explanation. My windows toolchain with stlink debug on WSL was a bit tricky but got there in the end. Anyone struggling: multiarch-gdb symlinked to arm + usbipd.

  • @CuriousCyclist
    @CuriousCyclist Год назад

    Thank you for taking the time to make this video. Really good educational content. 👍

  • @alexyoung6418
    @alexyoung6418 Год назад +4

    I like how you linked two otherwise separated projects together to paint a complete picture of how they are allocated in the memory and how they work together, or at least not against each other. It's worth pointing out that some applications also jump back to the bootloader to trigger the firmware update feature. Assuming the worst scenario where main application hadn't done a good cleanup by resetting the interrupt vector offset register, it's recommended to force reset that exact register in the very beginning of the bootloader execution.

    • @LowByteProductions
      @LowByteProductions  Год назад +1

      Great point, the bootloader should also explicitly set the vector table offset.

  • @フィリップフーベル
    @フィリップフーベル Год назад +1

    Again, I realy like your project! Some improvement-suggestions: to really understand the content of the jump_to_main function (better name would be jump_to_app_main), it is essential that the listener understand that the linker will do all the main work for us. The address of the jump_fn which we are digging out of the addresses with the pointer joggling is correct BECAUSE the linker puts it there (will/has put it there) with the linker script of the app.

  • @stevekoehn1675
    @stevekoehn1675 Год назад

    Your thinking is so clear, your explanations so clear and logical I Get It! (I'm older and learning on my own so I need all the help I can get, ha)

  • @anshulmaurya6913
    @anshulmaurya6913 Год назад

    It's been done really really nicely - Thank You

  • @IndianNatiional
    @IndianNatiional Месяц назад

    This is a great video !!

  • @lorito6995
    @lorito6995 4 месяца назад

    many thanks!

  • @johnhansson8646
    @johnhansson8646 Год назад +1

    Regarding the padding of the bootloader to 32kb, couldn’t you configure that in your linker script, so that it is done for you when linking?

  • @gionag
    @gionag Год назад +1

    in linker, why don't use FILL to pad the bootloader ?

  • @u0000-u2x
    @u0000-u2x Год назад

    Amazing content as usual. Thank you for sharing.

  • @john999
    @john999 Год назад

    Thank you very much for your tutorial. It is so kind of you, to give us this free and easy to understand introduction into bootloaders.
    When I started to experiment with bootloaders, I was overwhelmed by the whole documentation of the chip. And of course it did not work, so I was thrown off, not knowing why it failed.
    With this start-to-finish mini-demo it is a lot easier to get going and building from that on forward.
    Would be nice if you could mention some ways of including your handmade changes of the linker script to IDEs (e.g. Eclipse). Like, where you can use the settings of the project to change the memory layout or add sections.
    Maybe this helps other to include your bootloader to their current project w/o too much editing of linker files that are handled by the autogen of the IDE.

    • @LowByteProductions
      @LowByteProductions  Год назад

      Thank you John. Indeed - I'll point out whenever I'm making changes to linkerscripts etc, and what those changes imply.
      As for playing well with other IDEs, I'm not sure how much help I can be 😄 I try to avoid eclipse wherever I can! I am familiar with STM32CubeIDE however, and how it can be configured to use a makefile build, which gives a lot more control. There are still autogenerated files to deal with, but typically the linkerscript is not one of the files which is not regenerated across builds. I'll add it to the list of future topics.

  • @aleph-ukraine
    @aleph-ukraine Месяц назад

    I am interested in seeing how to create a custom bootloader for Cortex A7 (32-bit) booted from NAND (also EMMC)memory - because a lot of devices around us, old routers, phones, IP cameras, etc, can have second-life
    These two topics are hard, NAND and Cortex-A series

  • @MarvinPranajaya
    @MarvinPranajaya Год назад

    Great content! It has helped me learn so much about bootloaders. Just wondering why do you not set the Stack Pointer to the APP before jumping to APP. I believe we need to do so to utilize all the RAM space? I also realized the reset handler by Libopencm3 doesnt include a Stack Pointer intialisation.

  • @kilwo
    @kilwo Год назад +4

    Just wondering why you do a C++ function call in the bootloader? If you did an assembly jump (branch I think in ARM) then you could reset the vector table offset address before the jump and the main app wouldn't need to know anything about the size of the bootloader. The main app would just work as if it was the only thing in memory and you could change the bootloader size without needing to update the main app.

    • @LowByteProductions
      @LowByteProductions  Год назад +1

      First of all, thanks for taking the time to comment!
      I'm not sure what you mean by a C++ function call. Where possible I'm trying to avoid inline assembly - the compiler is usually (like 99% of the time) smart enough to do the right thing when expressed in C. For example, the process for getting the address of the reset vector, casting it to a void (*)(void) function pointer and calling it, results in basically the assembly you describe; figuring out an offset, getting the reset vector address indirectly, and using a bx (branch) instruction to jump to it.
      Your point about setting the vector table offset in the bootloader is a really good suggestion - I guess that does make a lot more sense!

    • @kilwo
      @kilwo Год назад

      @@LowByteProductions Thanks for taking the time to make the videos! I really enjoy low level stuff. Sorry, my thoughts were not 100% clear. The C++ function call I was referring to was just the fact that you cast the pointer into a function, and then call that. The reason I was thinking this should be an unconditional jump, or equivalent, was because a function call effects the stack, where a jump doesn’t. At least in the processors I have used. Once you have changed the vector offset register, you don’t want to do a call that will push onto the stack, as it will never be returned and will waste memory. Keep up the great videos.

    • @LowByteProductions
      @LowByteProductions  Год назад +4

      Ah OK I see what you mean. The C++ threw me off there - I don't think there is a relation to C++ here.
      You're right that you'll end up with an unecessary stack frame, but one of the first things that happens in the reset vector for both the bootloader and the main application is setting the stack pointer to an explicit value. That has the effect of essentially removing tany stack frames that would have been there previously, and so doing a cast/call isn't a problem. And even so, no arguments or other information is pushed to the stack (at least the way this is currently compiled), as the cast is a 0 argument function. Even if the blx instruction were used (branch and link), the return address of the function is still only placed into a cpu register, not the stack.
      I really enjoy digging into the details of this kind of thing, so the discussion is very much appreciated.

    • @ZeroPlayerGame
      @ZeroPlayerGame Год назад

      @@LowByteProductions I actually went and checked, and I'm sorry to say, but libopencm3's reset handler does none such. All it does is copy the .data section into RAM, and run global constructors.

  • @bobweiram6321
    @bobweiram6321 5 месяцев назад

    Is it possible to pad the bootloader using the assembler. You can create a data section and then include your bin .

  • @aymaneeljahrani2280
    @aymaneeljahrani2280 7 месяцев назад

    I’m queen on your videos !

  • @SumitAdep
    @SumitAdep 9 месяцев назад

    well explained

  • @VectorNodes
    @VectorNodes Год назад

    37:36 it stands for “bull shit stuff”

  • @angryman9333
    @angryman9333 Год назад

    you are a genius.

  • @dty999
    @dty999 Год назад +2

    I'm not a C programmer, but at 25:10, why don't you let the compiler do the pointer math for you, and use reset_vector[1] instead of adding 4 to the address?

    • @dty999
      @dty999 Год назад

      Or, strictly, I guess you should redefine reset_vector as vector_table, then vector_table[1] makes more sense!

    • @LowByteProductions
      @LowByteProductions  Год назад

      Definitely possible, and perhaps the better way to do it! Libopencm3 actually defines a structure for the vector table, so casting the address to a pointer of that structure, and referring to the reset vector by name would another option.

    • @dty999
      @dty999 Год назад

      Or better yet, just have something like void_fn* interrupt_table = (void_fn*)MAIN_APP_START_ADDRESS; and then you can just go interrupt_table[1](); to call it.

  • @LaSDetta
    @LaSDetta Год назад +1

    Really nice video! I was wondering if you could use the FILL command in the linker script to ensure that the bootloader binary is always 0x8000 bytes?

    • @LowByteProductions
      @LowByteProductions  Год назад +3

      Thanks Håkan! Yes this is definitely possible, but I think you'd need to change the semantics a little. To use fill, I think you'd need to define a memory region for the bootloader.
      Not a bad way of doing it at all, bit there is one advantage to the current approach that I'm not sure how to replicate using fill: its easy to wrap the bootloader.S code, and the vector_setup() function in #ifdefs, and pass a flad to the compiler like -DINCLUDE_BOOTLOADER. This way, you can conditionally include the bootloader blob in the application, or choose to omit it. Without the image, the main application would end up at 0x08000000, and no VTOR offset would be required.
      Not a deal breaker by any means, but something to consider.

  • @stati5tik
    @stati5tik Год назад

    30:30 who set the address to the reset_vector in reset_vector_entry? is this hardware specific?

    • @LowByteProductions
      @LowByteProductions  Год назад +1

      It's per-platform, indeed. On the STM32 Cortex-M4 chips it tends to be at 0x80000000, but another vendor might map the beginning of flash elsewhere

  • @saturdaysequalsyouth
    @saturdaysequalsyouth Год назад +1

    .bss is something like "block started by segment". It's probably an outdated term that was repurposed which is why it doesn't make sense.

  • @MohammadShikha
    @MohammadShikha Год назад +1

    Hey, thanks for the video. I'm currently following along with the STM32F767ZI and the vector offset macro SCB_VTOR seems to require the final vector table address (0x08008000U) rather than an offsetting value (0x8000U) in order for interrupts (like SYS_TICK) to work. I'm not sure as to why this is the case, am I missing something obvious?

    • @LowByteProductions
      @LowByteProductions  Год назад +3

      That shouldn't be the case - the VTOR is literally the "vector table offset register", i.e. it always specifies an offset.
      When you are moving between the bootloader and main application, you need to disable any interrupts that could occur before you're able to change the offset value.
      Just to check: you are using libopencm3 right? Because there is only one macro definition there, and it just points to a memory mapped io register.
      Edit: just checked the reference manual for cortex-m7, and indeed, the vtor specifies and offset from 0x00000000 - so yes it is effectively an absolute address. You'll have to adjust the M4 based code to account for this. I'm sure they're be other subtle detail changes too!

    • @twitchy9948
      @twitchy9948 6 месяцев назад

      How did you come with this? I have exact stm32, and after setting it 0x08008000U it worked, thanks!

  • @viniciusgabriellinden4724
    @viniciusgabriellinden4724 Год назад +1

    why not something like `void_fn jump = (void_fn) (MAIN_APP_START_ADDRESS + sizeof(int)); return jump();`? more concise and does not depend on the bit width...

    • @LowByteProductions
      @LowByteProductions  Год назад +1

      You're absolutely right, and I have a feeling you'll enjoy the next video in the series 😁

  • @ulysses_grant
    @ulysses_grant 11 месяцев назад

    I am a simple man.
    I see a random channel with a video of a guy dumping game boy cartridges and I subscribe.

    • @LowByteProductions
      @LowByteProductions  11 месяцев назад +1

      Me too buddy, me too.

    • @ulysses_grant
      @ulysses_grant 11 месяцев назад

      ​@@LowByteProductionsJust learned about your channel. Sent the link to my brother and he just "LowByte?! This guy is a monster!" lol.
      Definitely subscribed!