Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >KSCrash源码分析

KSCrash源码分析

原创
作者头像
用户2297838
修改于 2018-12-10 03:25:36
修改于 2018-12-10 03:25:36
5K0
举报
文章被收录于专栏:Adam笔记Adam笔记

0x01 安装过程

1.1 抛砖引玉

代码语言:txt
AI代码解释
复制
KSCrashInstallationStandard* installation = [KSCrashInstallationStandard sharedInstance];
installation.url = [NSURL URLWithString:@"http://put.your.url.here"];
[installation install];

以上代码是KSCrash的安装代码,[KSCrashInstallationStandard init]底层对自身的属性进行赋值,然后,进入[KSCrashInstallationStandard install],这里会调用[KSCrash init]方法来设置要初始化的Crash监控器,然后调用[KSCrash install],这里会根据前面的设置好要启动的监控器来启动哪些监控器。

代码语言:txt
AI代码解释
复制
- (void) install
{
    KSCrash* handler = [KSCrash sharedInstance];
    @synchronized(handler)
    {
        g_crashHandlerData = self.crashHandlerData;
        handler.onCrash = crashCallback;
        [handler install];
    }
}	

在安装监控器的前面,还设置了crash的回调(onCrash),最终,会调用void kscm_setActiveMonitors(KSCrashMonitorType monitorTypes)来启动监控器,monitorTypes就是我们一开始设定好的g_monitoring,在KSCrashMonitor里面,设定好了监控器数组g_monitors,通过数组里面的monitorType来跟我们设定好的g_monitors进行运算,运算出的结果来决定我们是否安装该monitorType对应的监控器,下面,分析各个监控器的安装过程。

1.2 Mach kernel exceptions

Mach内核异常,由枚举KSCrashMonitorTypeMachException表示,由KSCrashMonitor_MachException来实现相关方法,如果添加至全局枚举g_monitoring里面,则需要开启,通过通用Apistatic void setEnabled(bool isEnabled)最后调用installExceptionHandler

代码语言:txt
AI代码解释
复制
static bool installExceptionHandler()
{
    ...

//    备份异常端口
    KSLOG_DEBUG("Backing up original exception ports.");
    kr = task_get_exception_ports(thisTask,
                                  mask,
                                  g_previousExceptionPorts.masks,
                                  &g_previousExceptionPorts.count,
                                  g_previousExceptionPorts.ports,
                                  g_previousExceptionPorts.behaviors,
                                  g_previousExceptionPorts.flavors);
    
    if(kr != KERN_SUCCESS)
    {
        KSLOG_ERROR("task_get_exception_ports: %s", mach_error_string(kr));
        goto failed;
    }

    if(g_exceptionPort == MACH_PORT_NULL)
    {
//          分配新端口并赋予接收权限
        KSLOG_DEBUG("Allocating new port with receive rights.");
        kr = mach_port_allocate(thisTask,
                                MACH_PORT_RIGHT_RECEIVE,
                                &g_exceptionPort);
        if(kr != KERN_SUCCESS)
        {
            KSLOG_ERROR("mach_port_allocate: %s", mach_error_string(kr));
            goto failed;
        }
        
//          给端口添加发送权限
        KSLOG_DEBUG("Adding send rights to port.");
        kr = mach_port_insert_right(thisTask,
                                    g_exceptionPort,
                                    g_exceptionPort,
                                    MACH_MSG_TYPE_MAKE_SEND);
        if(kr != KERN_SUCCESS)
        {
            KSLOG_ERROR("mach_port_insert_right: %s", mach_error_string(kr));
            goto failed;
        }
    }
// 将端口设置为接受异常的端口
    KSLOG_DEBUG("Installing port as exception handler.");
    kr = task_set_exception_ports(thisTask,
                                  mask,
                                  g_exceptionPort,
                                  EXCEPTION_DEFAULT,
                                  THREAD_STATE_NONE);
    if(kr != KERN_SUCCESS)
    {
        KSLOG_ERROR("task_set_exception_ports: %s", mach_error_string(kr));
        goto failed;
    }

//     创建辅助异常线程
    KSLOG_DEBUG("Creating secondary exception thread (suspended).");
//    创建一个分离线程
    pthread_attr_init(&attr);
    attributes_created = true;
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    error = pthread_create(&g_secondaryPThread,
                           &attr,
                           &handleExceptions,
                           kThreadSecondary);
    if(error != 0)
    {
        KSLOG_ERROR("pthread_create_suspended_np: %s", strerror(error));
        goto failed;
    }
//     获取分离线程的线程id
    g_secondaryMachThread = pthread_mach_thread_np(g_secondaryPThread);
    ksmc_addReservedThread(g_secondaryMachThread);

//    创建主要异常线程。
    KSLOG_DEBUG("Creating primary exception thread.");
    error = pthread_create(&g_primaryPThread,
                           &attr,
                           &handleExceptions,
                           kThreadPrimary);
    if(error != 0)
    {
        KSLOG_ERROR("pthread_create: %s", strerror(error));
        goto failed;
    }
    pthread_attr_destroy(&attr);
    g_primaryMachThread = pthread_mach_thread_np(g_primaryPThread);
    ksmc_addReservedThread(g_primaryMachThread);

    KSLOG_DEBUG("Mach exception handler installed.");
    return true;

···
}		

然后,使用分离线程,执行以下方法

代码语言:txt
AI代码解释
复制
static void* handleExceptions(void* const userData)
{
    ...
    for(;;)
    {
        KSLOG_DEBUG("Waiting for mach exception");

        // 等待异常触发
        kern_return_t kr = mach_msg(&exceptionMessage.header,
                                    MACH_RCV_MSG,
                                    0,
                                    sizeof(exceptionMessage),
                                    g_exceptionPort,
                                    MACH_MSG_TIMEOUT_NONE,
                                    MACH_PORT_NULL);
        if(kr == KERN_SUCCESS)
        {
            break;
        }

        // Loop and try again on failure.
        KSLOG_ERROR("mach_msg: %s", mach_error_string(kr));
    }

   ...
}

1.3 Fatal signals

Fatal signals在代码中,以KSCrashMonitorTypeSignal代表是否开启,在KSCrashMonitor_Signal里面实现相关实现,通用通过通用APIstatic void setEnabled(bool isEnabled),最后调用的是installSignalHandler

1.4 C++ exceptions

在代码里面,以枚举KSCrashMonitorTypeCPPException代表C++异常,在类KSCrashMonitor_CPPException里面实现相关代码。在通用APIstatic void setEnabled(bool isEnabled)里面通过std::set_terminate(terminate_handler)设置回调函数。

1.5 Objective-C exceptions

在代码里面,以KSCrashMonitorTypeNSException表示枚举值,在KSCrashMonitor_NSException里面实现具体代码,在通用APIstatic void setEnabled(bool isEnabled)里面通过void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable)设置回调

1.6 Main thread deadlock

Main thread deadlock是监控主线程死锁异常,在代码里面以枚举KSCrashMonitorTypeMainThreadDeadlock表示,在类KSCrashMonitor_Deadlock里面实现相关代码,在static void setEnabled(bool isEnabled)里面通过调用KSCrashDeadlockMonitor的初始化来安装死锁监控。

1.7 Custom crashes

自定义Crash,在代码里面用KSCrashMonitorTypeUserReported表示,在类KSCrashMonitor_User作具体实现,这个可以自定义crash,跟上面不一样的是,自定义的crash没有标准的crash时机,需要自己定义,也就是,遇到某些特发情况,可以收集信息,然后塞进KSCrashMonitor_User这里面,做后续处理。

0x02 运行过程

2.1 捕获

2.1.1 Mach kernel exceptions

在Mach中,异常是通过内核的基础设施——消息传递机制处理的。异常由出错的线程或任务(通过msg_send())抛出,然后由一个处理程序(msg_recv())捕捉。处理程序可以处理异常,也可以清除异常(即将异常标记为完成并继续),还可以决定终止线程。


Mach异常处理模型和其他的异常处理模型不同,其他模型的异常处理程序运行在出错的线程的上下文中,而Mach的异常处理程序在不同的上下文运行异常处理程序,出错的线程向预先制定好的异常端口发送消息,然后等待应答。每一个任务都可以注册一个异常端口,这个异常端口会对同一个任务中的所有线程起效。此外,单个线程还可以通过thread_set_exception_prots注册自己的异常端口。


所以Mach kernel exceptions中,使用mach_task_self获取当前任务进程,因为Mach异常其实是一个消息转发的异常,所以需要消息接收权限,在初始化异常端口的时候就赋予了mach_port_allocate(thisTask,MACH_PORT_RIGHT_RECEIVE,&g_exceptionPort,然而后面还赋予改端口的MACH_MSG_TYPE_MAKE_SEND,这是后面的task_set_exception_ports要求这个权限,然后task_set_exception_ports将这个端口设置为目标任务的异常端口。


至此,已经捕获了异常,但是没有做任何处理,为此,需要使用mach_msg在异常端口创建一个活动监听者。异常处理可以有同一个程序的另外一个线程来完成。也可以有另外一个程序实现异常处理。launched注册的进程的异常端口就是这么做。所以在后面,分别起了两个分离线程g_secondaryMachThreadg_primaryPThread来等待异常触发。

代码语言:txt
AI代码解释
复制
for(;;)
    {
        KSLOG_DEBUG("Waiting for mach exception");
	// 消息循环,一直阻塞,直到收到一条消息,而且必须是一条异常消息。
    // 其他消息也不会到达异常端口。
        kern_return_t kr = mach_msg(&exceptionMessage.header,
                                    MACH_RCV_MSG,
                                    0,
                                    sizeof(exceptionMessage),
                                    g_exceptionPort,
                                    MACH_MSG_TIMEOUT_NONE,
                                    MACH_PORT_NULL);
        if(kr == KERN_SUCCESS)
        {
            break;
        }

        // Loop and try again on failure.
        KSLOG_ERROR("mach_msg: %s", mach_error_string(kr));
    }

Mach的等待异常的过程如下图:

37E95EFEF7A37779DA43AA40BC62D132.jpg
37E95EFEF7A37779DA43AA40BC62D132.jpg
2.1.2 Fatal signals

Mach已经通过异常机制提供了底层的陷阱处理,而BSD则在异常机制之上构建了信号处理机制。硬件产生的信号被Mach捕捉,然后转换为对应的UNIX信号。为了维护一个统一的机制,操作系统和用户尝试的信号首先被转换为Mach异常,然后再转换为信号(Signals),如下图所示:

7A6E63A9B9ED07F096D6EDB504F189C7.jpg
7A6E63A9B9ED07F096D6EDB504F189C7.jpg

可以看出,跟我们自定义的Mach异常捕获不一样的是在于捕获到Mach异常的处理上。


当BSD进程(用户态进程)被bsdinit_task()函数启动时,会设置一个名为ux_handle的Mach内核线程。而ux_handle,与上面的Mach异常捕捉基本类似,只是他处理的是讲Mach异常转换为信号。


硬件产生的信号始于处理器陷阱。处理器陷阱与平台有关。ux_exception负责将陷阱转换为信号。为了处理机器相关的情况,ux_exception会调用machine_exception首先尝试处理机器陷阱。如果这个函数无法转换信号,ux_exception则处理一般情况。


如果信号不是由硬件产生的,那么这个信号来源于两个API调用:kill或pthread_kill。这两个函数分别向进程发送信号。


综上,信号可以看做是对硬件异常跟软件异常的封装。 硬件软件的错误对应了相应的信号,在KSCrash中,对一下信号进行了注册回调。

代码语言:txt
AI代码解释
复制
static const int g_fatalSignals[] =
{
    SIGABRT,
    SIGBUS,
    SIGFPE,
    SIGILL,
    SIGPIPE,
    SIGSEGV,
    SIGSYS,
    SIGTRAP,
};
2.1.3 C++ exceptions

C++ exceptions使用系统封装好的函数std::set_terminate(CPPExceptionTerminate)来设置回调。

2.1.4 Objective-C exceptions

Objective-C exceptions使用了NSSetUncaughtExceptionHandler系统回调来调用。 需要注意的是,这里可能会出现覆盖注册的问题。

2.1.5 Main thread deadlock

首先,在子线程执行runMonitor方法,然后执行watchdogPulse方法,在watchdogPulse里面通过dispatch_async到主线程来复位标志位,来鉴别是否发生死锁。

2.2 记录

2.2.1 流程

采集:数据的采集,从Crash发生就开始,主要是采集系统信息,以及crash信息,crash的信息主要是包括堆栈地址,原因等。 流程:分别依次从reportbinary_imagesprocesssystemcrash这些字段的流程来记录,下面,详细说下符号还原。

符号采集

NSException中,直接获取callStackReturnAddresses地址堆栈。 在Mach异常中,堆栈来源是寄存器的地址,首先会获取当前pc寄存器的值地址,然后符号还原,然后再获取lr指针,然后进行符号还原,然后再获取当前的fp指针,符号还原,然后不断的重复递归fp指针,还原符号的操作,知道递归到当前地址为0或者前置帧为空 Signal异常中,堆栈获取跟Mach相同。

符号还原

符号还原的对象是地址,符号还原的核心代码在下面:首先,通过imageIndexContainingAddress方法来获取当前的传入地址所在的Image的index,怎么获取Image的index?

代码语言:txt
AI代码解释
复制
bool ksdl_dladdr(const uintptr_t address, Dl_info* const info)
{
    info->dli_fname = NULL;
    info->dli_fbase = NULL;
    info->dli_sname = NULL;
    info->dli_saddr = NULL;

    const uint32_t idx = imageIndexContainingAddress(address);
    if(idx == UINT_MAX)
    {
        return false;
    }
    const struct mach_header* header = _dyld_get_image_header(idx);
    const uintptr_t imageVMAddrSlide = (uintptr_t)_dyld_get_image_vmaddr_slide(idx);
    const uintptr_t addressWithSlide = address - imageVMAddrSlide;
    const uintptr_t segmentBase = segmentBaseOfImageIndex(idx) + imageVMAddrSlide;
    if(segmentBase == 0)
    {
        return false;
    }

    info->dli_fname = _dyld_get_image_name(idx);
    info->dli_fbase = (void*)header;

    // Find symbol tables and get whichever symbol is closest to the address.
    const STRUCT_NLIST* bestMatch = NULL;
    uintptr_t bestDistance = ULONG_MAX;
    uintptr_t cmdPtr = firstCmdAfterHeader(header);
    if(cmdPtr == 0)
    {
        return false;
    }
    for(uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++)
    {
        const struct load_command* loadCmd = (struct load_command*)cmdPtr;
        if(loadCmd->cmd == LC_SYMTAB)
        {
            const struct symtab_command* symtabCmd = (struct symtab_command*)cmdPtr;
            const STRUCT_NLIST* symbolTable = (STRUCT_NLIST*)(segmentBase + symtabCmd->symoff);
            const uintptr_t stringTable = segmentBase + symtabCmd->stroff;

            for(uint32_t iSym = 0; iSym < symtabCmd->nsyms; iSym++)
            {
                // If n_value is 0, the symbol refers to an external object.
                if(symbolTable[iSym].n_value != 0)
                {
                    uintptr_t symbolBase = symbolTable[iSym].n_value;
                    uintptr_t currentDistance = addressWithSlide - symbolBase;
                    if((addressWithSlide >= symbolBase) &&
                       (currentDistance <= bestDistance))
                    {
                        bestMatch = symbolTable + iSym;
                        bestDistance = currentDistance;
                    }
                }
            }
            if(bestMatch != NULL)
            {
                info->dli_saddr = (void*)(bestMatch->n_value + imageVMAddrSlide);
                if(bestMatch->n_desc == 16)
                {
                    // This image has been stripped. The name is meaningless, and
                    // almost certainly resolves to "_mh_execute_header"
                    info->dli_sname = NULL;
                }
                else
                {
                    info->dli_sname = (char*)((intptr_t)stringTable + (intptr_t)bestMatch->n_un.n_strx);
                    if(*info->dli_sname == '_')
                    {
                        info->dli_sname++;
                    }
                }
                break;
            }
        }
        cmdPtr += loadCmd->cmdsize;
    }
    
    return true;
}

首先,通过传入地址减去地址偏移,得到地址在Image中的位置,暂且称之为处理过的地址,然后遍历当前加载的所有Image,然后在Image内部,在遍历所有的LC,比对地址是否在当前LC的的地址范围里面,具体:遍历每个Image,然后获取每个LC,然后比对当前处理过的地址是否大于VMAdress地址并且小于VM Adress 加上VM Size,如果符合条件,就是我们要找的Image。

1141930346A32F24CC2B214D89BC9812.jpg
1141930346A32F24CC2B214D89BC9812.jpg

回到ksdl_dladdr,获取到地址所在的Image,再获取segment base address of the specified image,同样,需要遍历当前Image的LC,然后获取到当前Image的LC_SEGMENT(__LINKEDIT),解析下__LINKEDIT__LINKEDIT动态库链接器需要使用的信息,包括重定位信息(例如符号表,字符串表),绑定信息,懒加载信息等,获取到LC_SEGMENT(__LINKEDIT)则返回VM_Address的值减去File Offset的值。然后用返回值加上实际的内存偏移。其实这里应该是求的当前符号表、字符串表的基地址,公式: 实际位置 = 当前__LINKEDIT在MachO的基地址 - 当前__LINKEDIT的文件偏移 + 实际的偏移量(这里已经包括了文件偏移)。

376D0C1799192A611F1B4A7DEAAFFF33.jpg
376D0C1799192A611F1B4A7DEAAFFF33.jpg

然后,同样利用遍历LC来获取当前的符号表信息,然后遍历符号表:然后用逐步靠近目标地址的方法,来获取最佳匹配的地址。

C8FFD20CF1AA7855E01AA08A9AA2F41A.jpg
C8FFD20CF1AA7855E01AA08A9AA2F41A.jpg
DEED97C7798DF3D3FF6837AA328CBF16.jpg
DEED97C7798DF3D3FF6837AA328CBF16.jpg

获取到了最加的符号地址匹配,然后求出对应的字符串,一下是单个符号的结构体,所以,我们利用n_strx 也就是String Table Index就可以获取到字符串了,至此,单个地址还原完成,其他的以此类推。

代码语言:txt
AI代码解释
复制
struct nlist_64 {
    union {
        uint32_t  n_strx; /* index into the string table */
    } n_un;
    uint8_t n_type;        /* type flag, see below */
    uint8_t n_sect;        /* section number or NO_SECT */
    uint16_t n_desc;       /* see <mach-o/stab.h> */
    uint64_t n_value;      /* value of this symbol (or stab offset) */
};

整个捕获流程,如下图所示

8ED95119ABB64DCDA6FA374F6AFF4AFB.jpg
8ED95119ABB64DCDA6FA374F6AFF4AFB.jpg

0x03 参考资料

《深入解析Mac OS X & iOS操作系统》

《iOS逆向应用与安全》

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
仅有两名前端开发,联机小游戏一周内上线,如何做到?
它在上线四小时内用户数激增60倍,获得新华社力荐,开发过程中仅投入2个前端开发+1个美术+1个策划,这款小游戏里,单机玩法、邀请好友对战、在线匹配对战、排行榜、背景音乐音效等功能一应俱全。
泛互联网行业产品团队
2020/03/04
10.5K1
仅有两名前端开发,联机小游戏一周内上线,如何做到?
仅有两名前端开发,联机小游戏一周内上线,如何做到?
它在上线四小时内用户数激增60倍,获得新华社力荐,开发过程中仅投入2个前端开发+1个美术+1个策划,这款小游戏里,单机玩法、邀请好友对战、在线匹配对战、排行榜、背景音乐音效等功能一应俱全。
腾讯云开发TCB
2023/07/14
1.1K0
仅有两名前端开发,联机小游戏一周内上线,如何做到?
仅有两名前端开发,联机小游戏一周内上线,如何做到?
它在上线四小时内用户数激增60倍,获得新华社力荐,开发过程中仅投入2个前端开发+1个美术+1个策划,这款小游戏里,单机玩法、邀请好友对战、在线匹配对战、排行榜、背景音乐音效等功能一应俱全。
腾讯云开发TCB
2020/03/31
4.4K0
仅有两名前端开发,联机小游戏一周内上线,如何做到?
腾讯云大学大咖分享预告│小游戏开发课—答题游戏!
在8月19日的直播课中,我们了解到了广泛应用于各种回合制/策略、实时会话类游戏的小游戏联机对战引擎,以及如何用帧同步构建实时对战游戏。
可可爱爱没有脑袋
2019/08/20
2.9K0
腾讯云大学大咖分享预告│小游戏开发课—答题游戏!
腾讯云资深产品经理 | MGOBE:快速实现小游戏的多人联机对战玩法
8月17日,“小程序·云开发”系列沙龙(小游戏专场)圆满落幕。本期沙龙云+社区携手微信 & 云开发官方团队为大家揭秘爆款微信小游戏背后的技术,全面讲解小程序·云开发、实时数据库库及小游戏联机对战引擎,助力小游戏开发。下面是张小华老师针对如何快速的构建联机对战类小游戏,调用几个API接口即实现房间管理、在线匹配、帧同步、状态同步、实时游戏server等游戏组件,快速搭建属于自己的对战服的分享。
腾讯云开发者社区技术沙龙
2019/08/26
5.5K0
腾讯云资深产品经理 | MGOBE:快速实现小游戏的多人联机对战玩法
腾讯云为小游戏开发者升级工具箱 小游戏联机对战引擎免费用
12月23日,腾讯云宣布,除了给创意大赛的参赛者提供基础云资源,还将为参赛者提供更多工具支持。开发者在通过初赛后,可免费使用“小游戏联机对战引擎”。比赛结束后,获奖游戏中单日DAU在30万内还可持续免费使用小游戏联机对战引擎至2020年12月31日。
泛互联网行业产品团队
2019/12/23
2.3K0
腾讯云为小游戏开发者升级工具箱 小游戏联机对战引擎免费用
腾讯云大学大咖分享预告│如何轻松开发一款对战小游戏
2018年微信小程序游戏内部测试开放后,微信小游戏的数量大幅增加。随着王者荣耀、刺激战场多款游戏风靡,大家对MOBA、FPS等游戏玩法逐渐熟知,玩家在线上邀请好友、或者通过匹配系统和其他陌生人玩家一起展开一场酣畅淋漓的游戏。在这些游戏中,开发者们是如何实现创建房间、玩家邀请、自由匹配等多种交互场景的呢?
可可爱爱没有脑袋
2019/08/15
1.6K0
腾讯云大学大咖分享预告│如何轻松开发一款对战小游戏
腾讯云大学大咖分享 | 小游戏联机对战引擎实践
腾讯云大学本期直播课程邀请到了腾讯云Web前端工程师通过两个小游戏demo,讲解了小游戏联机对战引擎中帧同步和状态同步两种应用场景。「腾讯云大学」联合「云加社区」为大家整理了课程精彩干货!
可可爱爱没有脑袋
2019/09/11
4.6K0
腾讯云大学大咖分享 | 小游戏联机对战引擎实践
新华社独家采访三体云动,打卡“云健身”|腾讯SaaS加速器·学员动态
来源 | 新华社 腾讯SaaS加速器首期项目-三体云动 ---- 腾讯SaaS加速器 二期30席项目招募 报名方式 腾讯SaaS加速器,作为腾讯产业加速器的一个重要组成部分,旨在搭建腾讯与SaaS相关企业的桥梁,通过资本、技术、资源、商机等层面的扶持,从战略到场景落地全方位加速企业成长,助力产业转型升级。 二期招募正式开始,扫描 二维码 立刻报名 (或点击文末  “阅读原文”,直达报名入口) 详情介绍:寻找SaaS“潜力军”,腾讯SaaS加速器二期开启招募 今年8月8日是我国第十二个全民健身日,新
腾讯SaaS加速器
2020/08/14
1.1K0
追加10亿!腾讯宣布设立15亿元“战疫基金”
刚刚,腾讯宣布设立15亿元“抗击新型冠状病毒感染肺炎疫情综合保障基金”(以下简称战疫基金)。 这是腾讯在设立首期3亿元疫情防控基金、2亿元战疫开发者公益联盟资金池后,再次升级追加10亿元基金,为抗击疫情进行助力。 其中,战疫基金将专设3亿元的“致敬战疫人物基金”,用于对“战疫”一线做出特殊贡献人士的致敬和慰问。 ---- 腾讯15亿元战疫基金分别投入在物资支援、技术支援、人员关怀、科研与医疗事业等领域,是着眼于抗击当下疫情和防患未来疫情的一揽子救援和保障计划。 3亿采购紧缺物资,救前线燃眉之急
腾讯SaaS加速器
2020/06/09
3170
别在纠结“后端”开发了,联机小游戏还可以这样做!
本篇文章要感谢「银笑的尤里」从 9月28日腾讯云深圳「游戏开发的超“音”“速”」沙龙发来了重磅消息,下面 Shawn 重点介绍对个人开发者惊喜的“MGOBE” 联机对战引擎。
张晓衡
2019/10/14
2.7K0
别在纠结“后端”开发了,联机小游戏还可以这样做!
《玩游戏,学技术》第一讲:画饼
我之前写过一篇文章 我用消息队列做了个联机游戏 用 Pulsar 这款消息队列实现了一个比较简陋的炸弹人游戏
labuladong
2023/03/02
4640
《玩游戏,学技术》第一讲:画饼
腾讯云游戏行业整体解决方案
随着游戏行业的迅猛发展,游戏行业竞争日益加剧,好的用户体验度和快速反应能力成为游戏网站发展的关键。游戏行业整体解决方案将能够为游戏厂商提供优质全面便捷的服务。
可可爱爱没有脑袋
2020/01/03
4.4K0
腾讯云游戏行业整体解决方案
内容资讯小程序引发热潮,小游戏争先上榜 | 晓榜
知晓程序注: 「晓榜」重新改版上线啦! 随着越来越多小程序的出现,大量用户广泛使用小程序,以及更多投资机构关注小程序领域,知晓程序愈发感受到发掘具有成长潜力的小程序的价值。因此,作为小程序行业最早的榜单之一,这一次,「晓榜」重新出发! 接下来,「晓榜」将正式定位为「发现最具潜力的小程序」。榜单主要关注崭露头角和强势成长阶段的小程序,分别列为「新锐榜」、「成长榜」。榜单依据国内最早、最大的小程序应用商店「知晓商店」的上架应用数据,并基于知晓程序编辑团队权威推荐,将在此后每周、每月分别推出「周榜」和「月榜」,并
知晓君
2018/07/03
5150
直播授课战“疫”,乐享为学校助力!
新型冠状病毒疫情牵动着全国人的心,延迟开学成为不少省份的选择。开学可以延迟,但学业不能滞后。如何保证正常的教学进度,不让任何一个学生落下功课,成为所有学校面临的共同问题。 为了帮助各大学校保持正常的教学进度,腾讯乐享先全面开放各项应用能力,为学校提供直播授课、在线考试、信息收集、信息发布等功能。在疫情期间,我们已支持几千所学校的教学活动,如陕西中医药大学、江苏大学、江西临川二中、洪塘中心小学等等。 在线授课,学考结合 腾讯乐享支持两种在线授课方式:一种是通过腾讯乐享直播进行在线授课,一部手机就
腾讯乐享
2020/02/12
9720
直播授课战“疫”,乐享为学校助力!
简单几步,教你搭建一款联机游戏
联机游戏的社交属性强,玩家粘性高,但是相对单机游戏,联机游戏开发周期长、成本高,因此很多开发者选择开发单机游戏,然而投入大量开发时间和资源,单机游戏活跃度不温不火,玩家数量持续流失。本文利用两款小游戏案例介绍如何快速搭建联机玩法,帮助开发者短期低成本实现一款联机游戏。
腾讯游戏云
2021/01/04
7.4K1
简单几步,教你搭建一款联机游戏
周桂华(花叔):微信小游戏开发之路
8月17日,“小程序·云开发”系列沙龙(小游戏专场)圆满落幕。本期沙龙云+社区携手微信 & 云开发官方团队为大家揭秘爆款微信小游戏背后的技术,全面讲解小程序·云开发、实时数据库库及小游戏联机对战引擎,助力小游戏开发。下面是周桂华老师从个人学习路径构建出发,阐述一个非游戏开发者是如何快速学习微信小游戏开发并同时驱动团队对接小游戏业务的。
腾讯云开发者社区技术沙龙
2019/08/23
2.1K1
周桂华(花叔):微信小游戏开发之路
腾讯云开发者社区技术沙龙第25期回顾-“小程序·云开发”系列沙龙(小游戏专场)(文末附PPT)
2019年8月17日,由云+社区主办的第25期云+社区技术沙龙——“小程序·云开发”系列沙龙(小游戏专场),在广州南国酒店举行,本期沙龙云+社区携手微信 & 云开发官方团队为大家揭秘爆款微信小游戏背后的技术,全面讲解小程序·云开发、实时数据库库及小游戏联机对战引擎,助力小游戏开发。同时,现场还设有Workshop,分享小游戏用云实践,通过现场手把手实操,帮助大家快速开发出一款流畅稳定的小游戏。现场技术热情高涨,座无虚席、交流氛围浓厚。
腾讯云开发者社区技术沙龙
2019/08/21
2.6K1
腾讯云开发者社区技术沙龙第25期回顾-“小程序·云开发”系列沙龙(小游戏专场)(文末附PPT)
小游戏不是“大厂”特权
随着“羊了个羊”等小程序游戏的爆火,大家逐渐看到了小程序游戏赛道的潜力,也开始不满足于依托微信等各大平台的小游戏,开发者们希望自己的App也拥有小程序游戏的运行能力,那么这该如何实现呢? 近期FinC
Lydiasq
2022/12/27
4960
小游戏不是“大厂”特权
微信又是一次大更新,下拉多任务切换 各种有趣小游戏
'图文简评' 昨天,微信又迎来了一次更新 版本为6.6.1,现在可以在微信的小程序内直接玩更加生动复杂的游戏了,目前腾讯放出了几款游戏给大家尝鲜这个功能。 ▌小游戏 微信小游戏的特点很简单,点开即玩,无需下载任何APP并且现在支持联机对战,甚至还支持横屏游戏,运行速度比一些客户端游戏还要好。 更多小程序内的小游戏,可以在微信小程序搜索里面搜索“微信小游戏”。 目前有麻将,斗地主,坦克大战等众多腾讯开发的小游戏可以玩。 ▌小程序多任务 现在小程序入口也得到了改善,比之前好用多了!直接在微信主界面
企鹅号小编
2018/01/18
7920
微信又是一次大更新,下拉多任务切换 各种有趣小游戏
推荐阅读
相关推荐
仅有两名前端开发,联机小游戏一周内上线,如何做到?
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档