NiLuJe / FBInk

FrameBuffer eInker, a small tool & library to print text & images to an eInk Linux framebuffer
https://www.mobileread.com/forums/showthread.php?t=299110
GNU General Public License v3.0
332 stars 23 forks source link

Segfault/overflow in print_ot when drawing near the bottom of the screen #49

Closed mrichards42 closed 3 years ago

mrichards42 commented 3 years ago

Hello, thanks for a great project!

I've noticed that drawing text near the bottom of the screen causes occasional issues (either a segfault, or breaking font metrics enough that it thinks it has to scale to infinity). I've managed to come up with a script that can reproduce the issue, and I think I've pinpointed where the problem happens, but I don't know the code well enough to know what the fix should be :)

lua script ```lua #!/usr/bin/env luajit ffi = require("ffi") require("ffi/fbink_h") FBInk = ffi.load("/opt/lib/libfbink.so.1.0.0") print("Loaded FBInk " .. ffi.string(FBInk.fbink_version())) fbink_cfg = ffi.new("FBInkConfig") fbink_cfg.is_verbose = true fbfd = FBInk.fbink_open() FBInk.fbink_init(fbfd, fbink_cfg) font_cfg = ffi.new("FBInkOTConfig") FBInk.fbink_add_ot_font_v2("/usr/share/fonts/ttf/noto/NotoSerif-Regular.ttf", FBInk.FNT_REGULAR, font_cfg) FBInk.fbink_add_ot_font_v2("/usr/share/fonts/ttf/noto/NotoSerif-Bold.ttf", FBInk.FNT_BOLD, font_cfg) FBInk.fbink_add_ot_font_v2("/usr/share/fonts/ttf/noto/NotoSerif-Italic.ttf", FBInk.FNT_ITALIC, font_cfg) font_cfg.size_px = 48 font_cfg.padding = FBInk.HORI_PADDING -- Remarkable 2 with screen size 1404x1872 font_cfg.margins.left = 900 font_cfg.margins.top = 1547 font_cfg.margins.right = 302 font_cfg.margins.bottom = 25 for i = 1,2 do -- The second iteration returns early with an impossible line height -- Max BL: 38 Max Desc: -2147483648 Max LG: 0 => Max LH (according to metrics): -2147483610 FBInk.fbink_print_ot( fbfd, "Hello aaaa bbbbb cc ddddd eee ff g hhhhhhh iiii", font_cfg, fbink_cfg, nil) end ```
verbose log ``` Loaded FBInk v1.23.1-git for reMarkable [FBInk] Detected a reMarkable 2 (Zero Sugar) [FBInk] This device does not support HW inversion [FBInk] Running under the rm2fb compatibility shim (version 0.1), functionality may be limited [FBInk] Clock tick frequency appears to be 100 Hz [FBInk] Screen density set to 226 dpi [FBInk] Variable fb info: 1404x1872, 16bpp @ rotation: 0 (Upright, 0°) [FBInk] Fixed fb info: ID is "mxcfb", length of fb mem: 5256576 bytes & line length: 2808 bytes [FBInk] Fontsize set to 24x24 (IBM base glyph size: 8x8) [FBInk] Line length: 58 cols, Page size: 78 rows [FBInk] Pen colors set to #000000 for the foreground and #FFFFFF for the background Loading font data in a local FBInkOTFonts instance (0x6c1fc0) . . . Initialized libunibreak [FBInk] Font `/usr/share/fonts/ttf/noto/NotoSerif-Regular.ttf` loaded for style 'Regular' Loading font data in a local FBInkOTFonts instance (0x6c1fc0) . . . [FBInk] Font `/usr/share/fonts/ttf/noto/NotoSerif-Bold.ttf` loaded for style 'Bold' Loading font data in a local FBInkOTFonts instance (0x6c1fc0) . . . [FBInk] Font `/usr/share/fonts/ttf/noto/NotoSerif-Italic.ttf` loaded for style 'Italic' Printing OpenType text. Using fonts from a local FBInkOTFonts instance (0x6c1fc0) Unformatted text defaulting to Regular font style Max BL: 38 Max Desc: -10 Max LG: 0 => Max LH (according to metrics): 48 Finished looking for linebreaks Current Measured LW: 27 Line# 0 Current Measured LW: 46 Line# 0 Current Measured LW: 58 Line# 0 Current Measured LW: 69 Line# 0 Current Measured LW: 88 Line# 0 Current Measured LW: 89 Line# 0 Current Measured LW: 117 Line# 0 Current Measured LW: 137 Line# 0 Current Measured LW: 157 Line# 0 Current Measured LW: 177 Line# 0 Current Measured LW: 178 Line# 0 Current Measured LW: 207 Line# 0 Looking for a linebreak opportunity . . . Flagging a last-resort break @ #10 Found a break @ #10 Current Measured LW: 20 Line# 1 Current Measured LW: 42 Line# 1 Current Measured LW: 64 Line# 1 Current Measured LW: 86 Line# 1 Current Measured LW: 108 Line# 1 Current Measured LW: 110 Line# 1 Current Measured LW: 135 Line# 1 Current Measured LW: 152 Line# 1 Current Measured LW: 153 Line# 1 Current Measured LW: 184 Line# 1 Current Measured LW: 206 Line# 1 Looking for a linebreak opportunity . . . Flagging a last-resort break @ #20 Found a break @ #19 Current Measured LW: 22 Line# 2 Current Measured LW: 44 Line# 2 Current Measured LW: 66 Line# 2 Current Measured LW: 88 Line# 2 Current Measured LW: 110 Line# 2 Current Measured LW: 110 Line# 2 Current Measured LW: 137 Line# 2 Current Measured LW: 156 Line# 2 Current Measured LW: 175 Line# 2 Current Measured LW: 176 Line# 2 Current Measured LW: 201 Line# 2 Current Measured LW: 214 Line# 2 Looking for a linebreak opportunity . . . Flagging a last-resort break @ #30 Found a break @ #29 Current Measured LW: 16 Line# 3 Current Measured LW: 29 Line# 3 Current Measured LW: 26 Line# 3 Current Measured LW: 54 Line# 3 Current Measured LW: 54 Line# 3 Current Measured LW: 85 Line# 3 Current Measured LW: 107 Line# 3 Current Measured LW: 129 Line# 3 Current Measured LW: 151 Line# 3 Current Measured LW: 173 Line# 3 Current Measured LW: 195 Line# 3 Current Measured LW: 217 Line# 3 Looking for a linebreak opportunity . . . Flagging a last-resort break @ #40 Found a break @ #34 Current Measured LW: 22 Line# 4 Current Measured LW: 44 Line# 4 Current Measured LW: 66 Line# 4 Current Measured LW: 88 Line# 4 Current Measured LW: 110 Line# 4 Current Measured LW: 132 Line# 4 Current Measured LW: 154 Line# 4 Current Measured LW: 154 Line# 4 Current Measured LW: 174 Line# 4 Current Measured LW: 185 Line# 4 Current Measured LW: 196 Line# 4 Current Measured LW: 207 Line# 4 Looking for a linebreak opportunity . . . Flagging a last-resort break @ #45 Found a break @ #42 Current Measured LW: 11 Line# 5 Current Measured LW: 22 Line# 5 Current Measured LW: 33 Line# 5 Current Measured LW: 44 Line# 5 Flagging a mandatory break at EOS @ #46 6 lines to be printed Maximum printable height is 300 Actual print height is 288 Max LW: 202 Max LH: 48 Max BL: 38 FntSize: 48 Filled a #FF 0x288 rectangle @ (900, 1547) Filled a #FF 326x288 rectangle @ (1078, 1547) Finished printing line# 0 Filled a #FF 0x288 rectangle @ (900, 1595) Filled a #FF 351x288 rectangle @ (1053, 1595) Finished printing line# 1 Filled a #FF 0x288 rectangle @ (900, 1643) Filled a #FF 328x288 rectangle @ (1076, 1643) Finished printing line# 2 Filled a #FF 0x288 rectangle @ (900, 1691) Filled a #FF 450x288 rectangle @ (954, 1691) Finished printing line# 3 Filled a #FF 0x288 rectangle @ (900, 1739) Filled a #FF 350x288 rectangle @ (1054, 1739) Finished printing line# 4 Filled a #FF 0x288 rectangle @ (900, 1787) Filled a #FF 460x288 rectangle @ (944, 1787) Ran out of drawing area at the end of line# 5 after ~47 characters! Finished printing line# 5 Printed 6 visible lines Refreshing region from LEFT: 900, TOP: 1547, WIDTH: 202, HEIGHT: 288 waveform_mode is now 0x101 (AUTO) Printing OpenType text. Using fonts from a local FBInkOTFonts instance (0x6c1fc0) Unformatted text defaulting to Regular font style Max BL: 38 Max Desc: -2147483648 Max LG: 0 => Max LH (according to metrics): -2147483610 [FBInk] Max line height not set! Refreshing region from LEFT: 0, TOP: 0, WIDTH: 0, HEIGHT: 0 ```

I went in with gdb and figured out that the first time around all 3 of the font styles produce reasonable metrics, but the second time around the bold font results in an impossible scaling factor (infinity) here: https://github.com/NiLuJe/FBInk/blob/21023a92d99e68429d1f7172b4791f2d31eaa384/fbink.c#L5584

Looking further into stbtt_ScaleForPixelHeight, info->data + info->hhea + 4 and + 6 are both 0xffff, so the denominator in that function ends up being 0. In the first iteration, that location points to reasonable data, suggesting that somewhere before the second call to print_ot, that data got overwritten with 0xff.

gdb tells me that happens in fill_rect_RGB565, called from the right padding section of print_ot: https://github.com/NiLuJe/FBInk/blob/21023a92d99e68429d1f7172b4791f2d31eaa384/fbink.c#L6270-L6275

I don't understand what all the different variables are at that point, but based on the log it looks like that function gets called for each wrapped line of text. The logs below are taken from the full log, and I'm pretty sure they're each call to fill_rect_RGB565 to fill in the right padding. It looks like the height of the fill is the full calculated height of all 6 lines (48 pixels * 6 lines = 288), not the height of a single line, but the top of the rectangle keeps moving down the screen. The screen in this example is 1872 pixels tall, so all but the first call ends up filling 0xff bytes off the bottom of the screen. I assume eventually this overflows into the bold font's data.

Filled a #FF 326x288 rectangle @ (1078, 1547)
Filled a #FF 351x288 rectangle @ (1053, 1595)
Filled a #FF 328x288 rectangle @ (1076, 1643)
Filled a #FF 450x288 rectangle @ (954, 1691)
Filled a #FF 350x288 rectangle @ (1054, 1739)
Filled a #FF 460x288 rectangle @ (944, 1787)
NiLuJe commented 3 years ago

That's possibly going to be tricky to reproduce without the exact same device and the exact same fonts, but I'll see what I can come up with ;). Thanks for the detailed report, though, that's very helpful!

Yeah, it very much looks like the rectangle's height not being clamped to the remaining physical height is an issue here.

(As for why it uses the full height instead of a line's height, err, that's another good question ^^).

NiLuJe commented 3 years ago

Let me know if that did the trick... ;).

mrichards42 commented 3 years ago

Awesome, thanks that was fast! Looks like it took care of it :)

NiLuJe commented 3 years ago

Cool, many thanks for the detailed report, stepping through that probably wasn't terribly fun ;).

FWIW, this kind of stuff would have been harder to detect (if it had no visible impact on the drawn/refreshed stuff, I mean, which, err, wouldn't necessarily have been the case here ^^. I blame having written that bit of logic at 4AM ;p), on devices with an EPDC, because we write directly to the fb memory there, and at 8bpp, there's more than 3 virtual screen's worth of unused buffer past the visible screen, so the OOB write could still technically have written into "safe" memory. (Another kettle of fish @32bpp if the write is large enough to blow past the virtual screen, though ^^).

You don't get that luxury on the rM2 ;).