mono / libgdiplus

C-based implementation of the GDI+ API
http://www.mono-project.com/
MIT License
329 stars 171 forks source link

the functionality broken after install the latest version in alpine #694

Open lzw5399 opened 3 years ago

lzw5399 commented 3 years ago

dockerfile

FROM microsoft/dotnet:2.1-aspnetcore-runtime-alpine3.7 AS base
WORKDIR /docker
EXPOSE 5000
RUN echo 'http://mirrors.aliyun.com/alpine/v3.7/main' > /etc/apk/repositories && \
    echo 'http://mirrors.aliyun.com/alpine/v3.7/community' >> /etc/apk/repositories && \
    echo 'http://mirrors.aliyun.com/alpine/edge/testing' >> /etc/apk/repositories && \
    sed -i "s@\(.*http://\)[^/]*\(.*\)@\1mirror.sy\2@" /etc/apk/repositories && apk update && apk add tzdata terminus-font libgdiplus && \
    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone

there is a captcha functionality that depend the System.Drawing,the functionality works well before but it was broken recently, the error message

System.TypeInitializationException: 'The type initializer for 'Gdip' threw an exception.'

I found there is a new release of libgdiplus(libgdiplus-6.0.5-r1) at 2021-Jan-24 04:13:39.

And the early container that build from same dockerfile and install the libgdiplus-6.0.5-r0 works well

Is this package have any incompatible in alpine?

Regards.

lzw5399 commented 3 years ago
public Bitmap CreateBitmapByImgVerifyCode(string verifyCode, int width, int height)
{
    Random random = new Random(); 
    Bitmap bitmap = new Bitmap(width, height);
    Graphics g = Graphics.FromImage(bitmap);

    using (var font = new Font("Arial", 36, (FontStyle.Bold | FontStyle.Italic)))
    {
        SizeF totalSizeF = g.MeasureString(verifyCode, font);
        SizeF curCharSizeF;
        PointF startPointF = new PointF(((width - totalSizeF.Width) / verifyCode.Length) / 2, (height - totalSizeF.Height) / 2);
        g.Clear(Color.White); 
        for (int i = 0; i < verifyCode.Length; i++)
        {
            using (var brush = new LinearGradientBrush(new Point(0, 0), new Point(1, 1), Color.FromArgb(random.Next(255), random.Next(255), random.Next(255)), Color.FromArgb(random.Next(255), random.Next(255), random.Next(255))))
            {

                g.DrawString(verifyCode[i].ToString(), font, brush, startPointF);
                curCharSizeF = g.MeasureString(verifyCode[i].ToString(), font);
                startPointF.X += width / verifyCode.Length;
            }
        }
    }

    using (var pen = new Pen(Color.FromArgb(random.Next()), 5))
    {
        for (int i = 0; i < 20; i++)
        {
            int x1 = random.Next(bitmap.Width);
            int x2 = random.Next(bitmap.Width);
            int y1 = random.Next(bitmap.Height);
            int y2 = random.Next(bitmap.Height);
            g.DrawLine(pen, x1, y1, x2, y2);
        }
    }

    for (int i = 0; i < 100; i++)
    {
        int x = random.Next(bitmap.Width);
        int y = random.Next(bitmap.Height);
        bitmap.SetPixel(x, y, Color.FromArgb(random.Next()));
    }

    using (var pen = new Pen(Color.Silver))
    {
        g.DrawRectangle(pen, 0, 0, bitmap.Width - 1, bitmap.Height - 1); 
        g.Dispose();
        return bitmap;
    }
}

public byte[] CreateByteByImgVerifyCode(string verifyCode, int width, int height)
{
    using (var bitmap = CreateBitmapByImgVerifyCode(verifyCode, width, height))
    {
        using (var stream = new MemoryStream())
        {
            bitmap.Save(stream, ImageFormat.Jpeg);
            return stream.ToArray();
        }
    }
}
qmfrederik commented 3 years ago

That's odd. What output do you get if you export LD_DEBUG=libs (or ENV LD_DEBUG=libs in your Dockerfile) and then launch your .NET app?

lzw5399 commented 3 years ago

After added ENV LD_DEBUG=libs in dockerfile

The pod log doesn't output the library paths that trying file both in the application just started and the captcha API was invoked. It's weird, am I not use it correctly? FYI, Below is the error screenshot

qmfrederik commented 3 years ago

Are you using a multi-stage build by any chance? Make sure you set ENV LD_DEBUG=libs in your final container, and not just a build container (which is later thrown away).

You should probably also be able to set the LD_DEBUG environment variable via the --env command line argument if you launch the container with docker run, or in the env: field for your container if you use Kubernetes.

lzw5399 commented 3 years ago

yeap. the base container is also the final container.

and run kubectl exec enter the current kubernetes pod, run echo $LD_DEBUG, output lib correctly

qmfrederik commented 3 years ago

Ah yes, my bad. LD_DEBUG is a glbc-thing, and Alpine runs on musl. I'd recommend you use another mechanism, like strace, to see what's going on.

It's not my area of expertise so I can't help much, but this article seems like it might be useful: https://jvns.ca/blog/2014/03/10/debugging-shared-library-problems-with-strace/

lzw5399 commented 3 years ago

After comparing the two container which one is r1 and another r0. Found some clues

run ldd /usr/lib/libgdiplus.so.0 to trace the denpendency in r1 container, I found there is some error

same command in r0 container

And I copy the libgdiplus.so.0.0.0 file which the gdi package real link to from the r0 container to r1 container. then the captcha functionality recovered

darkms commented 3 years ago

Hi, I've also started having the same issue recently, it started happening around a month ago.

Using .NET Core 3.1 Alpine (FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine) and libgdiplus-dev apk package from dl-3.alpinelinux.org/alpine/edge/testing. The containers are hosted by AWS Fargate (1.4.0), the error seems to be consistent and spinning up new containers doesn't help. At the same time I was unable to reproduce the issue in plain docker on my MacOS. :(

ldd gives pretty much the similar output as above:

/lib/ld-musl-x86_64.so.1 (0x7f862cc18000) libpangocairo-1.0.so.0 => /usr/lib/libpangocairo-1.0.so.0 (0x7f862cb95000) libpango-1.0.so.0 => /usr/lib/libpango-1.0.so.0 (0x7f862cb57000) libgobject-2.0.so.0 => /usr/lib/libgobject-2.0.so.0 (0x7f862cb0d000) libglib-2.0.so.0 => /usr/lib/libglib-2.0.so.0 (0x7f862ca13000) libharfbuzz.so.0 => /usr/lib/libharfbuzz.so.0 (0x7f862c97e000) libcairo.so.2 => /usr/lib/libcairo.so.2 (0x7f862c891000) libjpeg.so.8 => /usr/lib/libjpeg.so.8 (0x7f862c7fb000) libtiff.so.5 => /usr/lib/libtiff.so.5 (0x7f862c791000) libgif.so.7 => /usr/lib/libgif.so.7 (0x7f862c786000) libpng16.so.16 => /usr/lib/libpng16.so.16 (0x7f862c756000) libX11.so.6 => /usr/lib/libX11.so.6 (0x7f862c634000) libexif.so.12 => /usr/lib/libexif.so.12 (0x7f862c5f1000) libfontconfig.so.1 => /usr/lib/libfontconfig.so.1 (0x7f862c5b5000) libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f862cc18000) libpangoft2-1.0.so.0 => /usr/lib/libpangoft2-1.0.so.0 (0x7f862c59e000) libfreetype.so.6 => /usr/lib/libfreetype.so.6 (0x7f862c4ec000) libfribidi.so.0 => /usr/lib/libfribidi.so.0 (0x7f862c4cf000) libffi.so.6 => /usr/lib/libffi.so.6 (0x7f862c4c6000) libpcre.so.1 => /usr/lib/libpcre.so.1 (0x7f862c469000) libintl.so.8 => /usr/lib/libintl.so.8 (0x7f862c459000) libgraphite2.so.3 => /usr/lib/libgraphite2.so.3 (0x7f862c432000) libpixman-1.so.0 => /usr/lib/libpixman-1.so.0 (0x7f862c3a1000) libxcb-shm.so.0 => /usr/lib/libxcb-shm.so.0 (0x7f862c39c000) libxcb.so.1 => /usr/lib/libxcb.so.1 (0x7f862c375000) libxcb-render.so.0 => /usr/lib/libxcb-render.so.0 (0x7f862c366000) libXrender.so.1 => /usr/lib/libXrender.so.1 (0x7f862c35a000) libXext.so.6 => /usr/lib/libXext.so.6 (0x7f862c347000) libz.so.1 => /lib/libz.so.1 (0x7f862c32d000) libexpat.so.1 => /usr/lib/libexpat.so.1 (0x7f862c30b000) libuuid.so.1 => /lib/libuuid.so.1 (0x7f862c302000) libbz2.so.1 => /usr/lib/libbz2.so.1 (0x7f862c2f3000) libXau.so.6 => /usr/lib/libXau.so.6 (0x7f862c2ee000) libXdmcp.so.6 => /usr/lib/libXdmcp.so.6 (0x7f862c2e6000) libbsd.so.0 => /usr/lib/libbsd.so.0 (0x7f862c2d0000) Error relocating /usr/lib/libgdiplus.so.0: hb_ot_metrics_get_position: symbol not found Error relocating /usr/lib/libgdiplus.so.0: pango_font_get_hb_font: symbol not found

The workaround of copying the older libgdiplus.so.0.0.0 file (from libgdiplus-dev, probably 6.0.5 or 6.0.4, whichever was previously on alpine apk feed in late 2020) does help (not sure if it's a legit thing to do and if that could cause any hard-to-trace issues in runtime).

Happy to provide more info if needed.

P.S. Would it be possible to restore previous version of libgdiplus-dev package back to alpine apk feed? If not, is there any way to build & install on (or for) alpine from github sources? (I've tried building on ubuntu, but the binaries it creates are significantly bigger than the ones in alpine feed packages)

darkms commented 3 years ago

I ended up building 6.0.5 (r0) using abuild to avoid any accidental binaries incompatibility in future, in case if anyone else needs it, here's how:

FROM alpine:latest AS build_libgdiplus
RUN apk add alpine-sdk
RUN adduser -D abuild_user \
    && addgroup abuild_user abuild
RUN mkdir /build \
    && chmod -R a+rwx /build
USER abuild_user
WORKDIR /build
COPY APKBUILD .
RUN abuild-keygen -a
RUN abuild -r -P /build
USER root
RUN chown -R root:root /build

FROM alpine:latest
COPY --from=build_libgdiplus /build/x86_64/ /libgdiplus-apk
RUN apk add --allow-untrusted /libgdiplus-apk/libgdiplus-6.0.5-r0.apk /libgdiplus-apk/libgdiplus-dev-6.0.5-r0.apk

Note: you'd need to have a file named APKBUILD in the working directory of where you build that dockerfile, the contents can be downloaded from alpine repo: https://gitlab.alpinelinux.org/alpine/aports/-/blob/44b1843ba5ebd29dc4c66b4269cb679bd14fad4b/testing/libgdiplus/APKBUILD (this one is 6.0.5-r0).